@symbiote-native/solid 1.0.0 → 2.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 (106) hide show
  1. package/README.md +9 -10
  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 +3 -3
  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 +9 -4
  15. package/build/components/modal.jsx +6 -6
  16. package/build/components/{pressable.d.ts → pressable-props.d.ts} +8 -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 +8 -0
  31. package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
  32. package/build/components/touchable-props.d.ts +18 -0
  33. package/build/components/touchable-props.js +1 -0
  34. package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +6 -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 +87 -98
  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 +1 -1
  47. package/build/descriptor-to-solid.js +1 -1
  48. package/build/index.d.ts +1 -0
  49. package/build/index.js +11 -3
  50. package/build/jsx-runtime.d.ts +39 -2
  51. package/build/modules/animated/index.d.ts +0 -4
  52. package/build/modules/animated/index.js +16 -22
  53. package/build/modules/image.d.ts +3 -0
  54. package/build/modules/image.js +12 -0
  55. package/build/register.d.ts +1 -1
  56. package/build/register.js +12 -28
  57. package/build/renderer.js +79 -16
  58. package/build/utils/stable-keys.d.ts +1 -1
  59. package/build/utils/stable-keys.js +6 -5
  60. package/metro-css-parser.cjs +2 -1
  61. package/package.json +5 -10
  62. package/babel-lower-host-primitives.cjs +0 -323
  63. package/build/components/activity-indicator/index.android.d.ts +0 -2
  64. package/build/components/activity-indicator/index.android.js +0 -11
  65. package/build/components/activity-indicator/index.d.ts +0 -1
  66. package/build/components/activity-indicator/index.ios.d.ts +0 -2
  67. package/build/components/activity-indicator/index.ios.js +0 -10
  68. package/build/components/activity-indicator/index.js +0 -4
  69. package/build/components/activity-indicator/shared.d.ts +0 -8
  70. package/build/components/activity-indicator/shared.js +0 -51
  71. package/build/components/button.d.ts +0 -7
  72. package/build/components/button.jsx +0 -45
  73. package/build/components/image-background.jsx +0 -74
  74. package/build/components/image.d.ts +0 -9
  75. package/build/components/image.js +0 -75
  76. package/build/components/input-accessory-view.jsx +0 -39
  77. package/build/components/pressable.jsx +0 -211
  78. package/build/components/refresh-control.jsx +0 -36
  79. package/build/components/safe-area-view.d.ts +0 -11
  80. package/build/components/safe-area-view.jsx +0 -24
  81. package/build/components/scroll-view/index.android.d.ts +0 -2
  82. package/build/components/scroll-view/index.android.js +0 -11
  83. package/build/components/scroll-view/index.d.ts +0 -2
  84. package/build/components/scroll-view/index.ios.d.ts +0 -2
  85. package/build/components/scroll-view/index.ios.js +0 -8
  86. package/build/components/scroll-view/index.js +0 -9
  87. package/build/components/scroll-view/shared.jsx +0 -334
  88. package/build/components/scroll-view/sticky-header.d.ts +0 -18
  89. package/build/components/switch/index.android.d.ts +0 -2
  90. package/build/components/switch/index.android.js +0 -12
  91. package/build/components/switch/index.d.ts +0 -1
  92. package/build/components/switch/index.ios.d.ts +0 -2
  93. package/build/components/switch/index.ios.js +0 -11
  94. package/build/components/switch/index.js +0 -4
  95. package/build/components/switch/shared.d.ts +0 -11
  96. package/build/components/switch/shared.js +0 -87
  97. package/build/components/text-input.d.ts +0 -10
  98. package/build/components/text-input.js +0 -290
  99. package/build/components/text.jsx +0 -27
  100. package/build/components/touchable/index.d.ts +0 -22
  101. package/build/components/touchable/index.jsx +0 -247
  102. package/build/components/touchable-native-feedback/index.d.ts +0 -16
  103. package/build/components/touchable-native-feedback/index.jsx +0 -68
  104. package/build/components/view.jsx +0 -49
  105. package/build/state-style.d.ts +0 -1
  106. package/build/state-style.js +0 -13
@@ -1,9 +1,46 @@
1
- import type { ISymbioteIntrinsic } from '@symbiote-native/components';
1
+ import type { ICrossTypedIntrinsics } from '@symbiote-native/components';
2
2
  import type { ISymbioteNode } from '@symbiote-native/engine';
3
+ import type { IViewProps } from './components/view-props';
4
+ import type { ITextProps } from './components/text-props';
5
+ import type { IPressableProps } from './components/pressable-props';
6
+ import type { IButtonProps } from './components/button-props';
7
+ import type { IImageProps } from './components/image-props';
8
+ import type { IImageBackgroundProps } from './components/image-background-props';
9
+ import type { IInputAccessoryViewProps } from './components/input-accessory-view-props';
10
+ import type { IRefreshControlProps } from './components/refresh-control-props';
11
+ import type { ISafeAreaViewProps } from './components/safe-area-view-props';
12
+ import type { ITouchableNativeFeedbackProps } from './components/touchable-native-feedback/touchable-native-feedback-props';
13
+ import type { ITouchableOpacityProps, ITouchableHighlightProps } from './components/touchable-props';
14
+ import type { ITouchableWithoutFeedbackProps } from './components/touchable-without-feedback/touchable-without-feedback-props';
15
+ import type { IScrollViewProps } from './components/scroll-view-props';
16
+ import type { ISwitchProps } from './components/switch-props';
17
+ import type { ITextInputProps } from './components/text-input-props';
18
+ import type { IModalProps } from './components/modal';
19
+ import type { IActivityIndicatorProps } from './components/activity-indicator-props';
3
20
  type ISymbioteHostAttributes = {
4
21
  readonly children?: JSX.Element;
5
22
  readonly [attribute: string]: unknown;
6
23
  };
24
+ interface ICrossedPrimitiveProps {
25
+ view: IViewProps;
26
+ text: ITextProps;
27
+ pressable: IPressableProps;
28
+ button: IButtonProps;
29
+ image: IImageProps;
30
+ 'image-background': IImageBackgroundProps;
31
+ 'input-accessory-view': IInputAccessoryViewProps;
32
+ 'refresh-control': IRefreshControlProps;
33
+ 'safe-area-view': ISafeAreaViewProps;
34
+ 'touchable-native-feedback': ITouchableNativeFeedbackProps;
35
+ 'touchable-opacity': ITouchableOpacityProps;
36
+ 'touchable-highlight': ITouchableHighlightProps;
37
+ 'touchable-without-feedback': ITouchableWithoutFeedbackProps;
38
+ 'scroll-view': IScrollViewProps;
39
+ switch: ISwitchProps;
40
+ 'text-input': ITextInputProps;
41
+ modal: IModalProps;
42
+ 'activity-indicator': IActivityIndicatorProps;
43
+ }
7
44
  export declare namespace JSX {
8
45
  type Element = ISymbioteNode | IArrayElement | IFunctionElement | (string & {}) | number | boolean | null | undefined;
9
46
  interface IArrayElement extends Array<Element> {
@@ -20,7 +57,7 @@ export declare namespace JSX {
20
57
  }
21
58
  interface IntrinsicAttributes {
22
59
  }
23
- interface IntrinsicElements extends Record<ISymbioteIntrinsic, ISymbioteHostAttributes> {
60
+ interface IntrinsicElements extends ICrossTypedIntrinsics<ISymbioteHostAttributes, ICrossedPrimitiveProps> {
24
61
  }
25
62
  }
26
63
  export {};
@@ -44,10 +44,6 @@ export declare const Animated: {
44
44
  event: typeof event;
45
45
  forkEvent: typeof forkEvent;
46
46
  unforkEvent: typeof unforkEvent;
47
- View: (props: import("./create-animated-component").IAnimatedComponentProps) => import("@symbiote-native/solid/jsx-runtime").JSX.Element;
48
- Text: (props: import("./create-animated-component").IAnimatedComponentProps) => import("@symbiote-native/solid/jsx-runtime").JSX.Element;
49
- Image: (props: import("./create-animated-component").IAnimatedComponentProps) => import("@symbiote-native/solid/jsx-runtime").JSX.Element;
50
- ScrollView: IAnimatedComponent;
51
47
  FlatList: IAnimatedComponent;
52
48
  SectionList: IAnimatedComponent;
53
49
  createAnimatedComponent: typeof createAnimatedComponent;
@@ -1,19 +1,25 @@
1
- // The Animated namespace for @symbiote-native/solid. createAnimatedComponent applied to this
2
- // adapter's primitives gives the six animated components RN exposes; the value graph, easing and
3
- // imperative drivers come from @symbiote-native/engine (framework-agnostic), so both halves meet
4
- // here in one object and `Animated.timing(new Animated.Value(0), …).start()` reads as it does in
5
- // RN. Solid's JSX takes the dotted form directly — `<Animated.View/>` compiles to
6
- // createComponent(Animated.View, …), no local alias needed.
1
+ // The Animated namespace for @symbiote-native/solid. The value graph, easing and imperative drivers
2
+ // come from @symbiote-native/engine (framework-agnostic), so both halves meet here in one object and
3
+ // `Animated.timing(new Animated.Value(0), …).start()` reads as it does in RN.
4
+ //
5
+ // THERE IS NO `Animated.View` / `Animated.Text` / `Animated.Image` / `Animated.ScrollView`, and
6
+ // nothing replaced any of them: `<view style={{ opacity: someAnimatedValue }}>` is the whole API
7
+ // for style, and `<scroll-view onScroll={Animated.event(…)}>` is the whole API for a native-driven
8
+ // scroll listener. The engine resolves an AnimatedNode written into any prop of any host node
9
+ // (`core/engine/src/animated/host-binding.ts`) — it publishes the current value, subscribes the
10
+ // leaf, writes each frame through its own targeted setNativeProps commit, and releases at the
11
+ // commit sweep; `setEventListener` calls `bindAnimatedEvent` on any `on*` prop for EVERY host node,
12
+ // not just a scroll one. Those four were aliases of the wrappers, and a wrapper is a tag now, which
13
+ // is not a value `<Animated.X>` can call.
14
+ //
15
+ // The two below REMAIN because they are tier 3: a render prop decides the list family's output
16
+ // shape in JS, so there is no tag for `<Animated.X>` to be an alias of.
7
17
  //
8
18
  // The NATIVE DRIVER is not this module's doing and must not be re-derived here. A
9
19
  // useNativeDriver animation promotes the value graph itself (animations/base.ts ->
10
20
  // value.__startNativeAnimation -> graph.__makeNative), and the props leaf follows automatically
11
21
  // through graph.__addChild. This file only assembles the surface.
12
22
  import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
13
- import { View } from '../../components/view.jsx';
14
- import { Text } from '../../components/text.jsx';
15
- import { Image } from '../../components/image.js';
16
- import { ScrollView } from '../../components/scroll-view';
17
23
  import { FlatList } from '../../components/flat-list/index.jsx';
18
24
  import { SectionList } from '../../components/section-list/index.jsx';
19
25
  import { createAnimatedComponent } from './create-animated-component.jsx';
@@ -21,11 +27,6 @@ export { createAnimatedComponent } from './create-animated-component.jsx';
21
27
  // The pure graph leaves live in the engine (they extend AnimatedWithChildren, no framework);
22
28
  // re-exported so this adapter's Animated surface matches React's.
23
29
  export { AnimatedProps, AnimatedStyle, AnimatedTransform, } from '@symbiote-native/engine';
24
- // View/Text/Image are pure host primitives with no cycle back to here, so wrap them eagerly.
25
- const AnimatedView = createAnimatedComponent(View);
26
- const AnimatedText = createAnimatedComponent(Text);
27
- const AnimatedImage = createAnimatedComponent(Image);
28
- let animatedScrollView;
29
30
  let animatedFlatList;
30
31
  let animatedSectionList;
31
32
  // The live, JS-driven driver half. RN's AnimatedImplementation.
@@ -58,13 +59,6 @@ const liveDrivers = {
58
59
  // spreads `...Animated` over the same component getters.
59
60
  const drivers = Platform.isDisableAnimations ? AnimatedMock : liveDrivers;
60
61
  export const Animated = {
61
- View: AnimatedView,
62
- Text: AnimatedText,
63
- Image: AnimatedImage,
64
- get ScrollView() {
65
- animatedScrollView ??= createAnimatedComponent(ScrollView);
66
- return animatedScrollView;
67
- },
68
62
  get FlatList() {
69
63
  animatedFlatList ??= createAnimatedComponent(FlatList);
70
64
  return animatedFlatList;
@@ -0,0 +1,3 @@
1
+ import { type IImageStatics } from '@symbiote-native/components';
2
+ export type { IImageStatics };
3
+ export declare const Image: IImageStatics;
@@ -0,0 +1,12 @@
1
+ // RN's Image STATICS — `getSize`, `prefetch`, `queryCache`, `abortPrefetch`, `resolveAssetSource`.
2
+ //
3
+ // IN `modules/`, NOT `components/`. The primitive is the `<image>` tag and its fold runs in the
4
+ // engine (`behaviors/image.ts`); what survives under the name `Image` is an imperative API with no
5
+ // view, which is what this bucket is for. Sitting in `components/` was what made a statics object
6
+ // read as a component.
7
+ //
8
+ // A NAMESPACE OBJECT rather than five bare functions, deliberately: `Alert`, `Share`, `Linking`
9
+ // and `Vibration` all take that shape, and an app porting from RN writes `Image.getSize(...)`
10
+ // verbatim. Shared verbatim via `imageStatics`, the same source every other adapter attaches.
11
+ import { imageStatics } from '@symbiote-native/components';
12
+ export const Image = imageStatics;
@@ -1 +1 @@
1
- export {};
1
+ import '@symbiote-native/components/register';
package/build/register.js CHANGED
@@ -1,31 +1,15 @@
1
1
  // Side-effect ONLY. Exports nothing, and must never be re-exported from the barrel.
2
2
  //
3
- // The engine's host-behavior registry is reached through a `register*` call, and CLAUDE.md records
4
- // what happens to one of those behind a barrel: Metro turns on `inlineRequires` for PRODUCTION
5
- // only, moving a `require` down to the first place its binding is used as a VALUE, and
6
- // `export { X } from './x'` compiles to a lazy getter. If nothing ever names the module as a value,
7
- // it never evaluates and the registration silently never happens — in Release builds alone,
8
- // invisible to tsc, to vitest, and to grepping the bundle.
3
+ // The fourteen calls live once, in `@symbiote-native/components/register` — they were identical in
4
+ // all five adapters, and each copy carried a migration ledger justifying a call against a wrapper
5
+ // that no longer exists. What is per-adapter is only the SPELLING of the import below: every
6
+ // `*-tag.test.*` here reaches the registry through `import './register';`, and this adapter's own
7
+ // barrel guard pins that line.
9
8
  //
10
- // A bare `import './register';` next to a re-export of the same specifier does NOT help: Babel
11
- // merges the two into one dependency and the merged dependency stays lazy. The shape that works is
12
- // this one — a module imported ONLY for its side effect and never re-exported — the same shape
13
- // `packages/slider/src/{react,vue,svelte,angular}/index.ts` uses. `barrel-side-effect.test.ts` is
14
- // what stops a later tidy-up from "fixing" the bare import into a re-export.
15
- import { registerImageBehavior, registerInputAccessoryViewBehavior, registerPressableBehavior, registerSwitchBehavior, registerTextInputBehavior, } from '@symbiote-native/components';
16
- registerPressableBehavior();
17
- // Only the LOWERED tags carry this — the wrapper renders `symbiote-text-input-managed` and
18
- // keeps running its own lifecycle. One owner per node; see `component-names/shared.ts`.
19
- registerTextInputBehavior();
20
- // Same reason as TextInput: the wrapper renders `symbiote-switch-managed` and runs its own
21
- // lastNativeReport/snap-back lifecycle, so the engine's copy attaches only to the bare tag.
22
- registerSwitchBehavior();
23
- // Image owns no runtime — its behavior is a prop FOLD and nothing else, and it is registered on the
24
- // same `symbiote-image` the wrapper already emits rather than on a `-managed` twin. That is safe
25
- // only because the mapping is idempotent, which `core/components/src/behaviors/image.test.ts`
26
- // asserts rather than assumes; a wrapper-built node simply folds a second time and nothing moves.
27
- registerImageBehavior();
28
- // Fold-only, and it shares the wrapper's tag for the same reason Image does: the mapping has no
29
- // aliasing at all, so a wrapper-built node folding a second time moves nothing.
30
- // `core/components/src/behaviors/input-accessory-view.test.ts` asserts that rather than assuming it.
31
- registerInputAccessoryViewBehavior();
9
+ // WHY A SIDE-EFFECT SUBPATH AND NOT A BARREL EXPORT. Metro turns on `inlineRequires` for PRODUCTION
10
+ // only: it moves a `require` down to the first place its binding is used as a VALUE, and
11
+ // `export { X } from './x'` compiles to a lazy getter. A registration reached that way never
12
+ // evaluates in a Release build — invisible to tsc, to vitest, and to grepping the bundle. A bare
13
+ // `import` placed NEXT TO a re-export of the same specifier does not help either: Babel merges the
14
+ // two into one dependency and the merged dependency stays lazy.
15
+ import '@symbiote-native/components/register';
package/build/renderer.js CHANGED
@@ -18,7 +18,7 @@
18
18
  // ref) rather than read off the docs.
19
19
  import { createRenderer } from 'solid-js/universal';
20
20
  import { appendChild, createAnchor, createElement as createEngineElement, createRawText, dlog, insertBefore, removeChild as removeEngineChild, routeProp, setNodePressed, setProp as setEngineProp, setText as setEngineText, toPublicInstance, RAW_TEXT_COMPONENT, SymbioteSurface, } from '@symbiote-native/engine';
21
- import { descriptorFor } from '@symbiote-native/components';
21
+ import { descriptorFor, TEXT_INPUT_MULTILINE_TAG, } from '@symbiote-native/components';
22
22
  function isSurface(node) {
23
23
  return node instanceof SymbioteSurface;
24
24
  }
@@ -129,16 +129,10 @@ function foldTextValue(node, key, value) {
129
129
  const fold = TEXT_FOLDS.get(key);
130
130
  return fold === undefined ? value : fold(value);
131
131
  }
132
- // The alias fold, at the RENDERER and not only in the transform — the defect class Angular paid for
133
- // twice on 2026-08-31. A lowered element inherits nothing the component wrapper did, and the
134
- // compile-time rename in `babel-lower-host-primitives.cjs` covers exactly the call sites the
135
- // transform REWROTE: `<View id={x} />` is fine (the attribute name is renamed before the preset
136
- // compiles it, dynamic value included), but a hand-written `<symbiote-view id="x">` is not, and it
137
- // committed `id` — a key Fabric does not know — while the component committed `nativeID`. Measured
138
- // by mounting both forms and diffing committed key NAMES; totals were identical and said nothing.
139
- //
140
- // The transform's rename STAYS. It is not redundant: it means the markup path arrives here already
141
- // spelled `nativeID`, so the common case never takes the branch below with a key to rewrite.
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.
142
136
  //
143
137
  // One string comparison rather than a Map lookup, because this sits on the per-prop write path —
144
138
  // 32 001 prop writes on a benchmark create, where a Map.get is the kind of cost the engine spent
@@ -147,8 +141,42 @@ function foldTextValue(node, key, value) {
147
141
  // both constants from `HOST_PRIMITIVES` and fails the moment a second pair appears.
148
142
  const ALIAS_FROM = 'id';
149
143
  const ALIAS_TO = 'nativeID';
150
- function foldAliasKey(name) {
151
- return name === ALIAS_FROM ? ALIAS_TO : name;
144
+ // `multiline` selects between TWO Fabric views, so the TAG decides and no prop write moves a node
145
+ // between them. Two of the three paths that can build the node resolve it earlier — the wrapper
146
+ // CONSUMES the prop to pick its intrinsic, a lowering transform reads a literal at compile time —
147
+ // and an author writing the tag by hand has neither, which leaves two silent divergences.
148
+ //
149
+ // The shared behavior (`core/components/src/behaviors/text-input.ts`) already makes the PAYLOAD
150
+ // follow the tag. The complaint has to live here instead of there: `foldPayload` runs inside the
151
+ // commit, so a throw from it lands a tick later as an uncaught exception with no frame naming the
152
+ // call site — measured, a test awaiting the mount sees `nothing committed` rather than the error.
153
+ const MULTILINE_PROP = 'multiline';
154
+ function assertMultilineMatchesTag(node, value) {
155
+ if ((value === true) === (node.props[MULTILINE_PROP] === true))
156
+ return;
157
+ throw new Error(`multiline={${String(value)}} contradicts the tag: <text-input> and ` +
158
+ `<text-input-multiline> are different Fabric views and no prop write moves a node ` +
159
+ `between them. Pick the tag (a runtime choice needs a <Show> around both).`);
160
+ }
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;
152
180
  }
153
181
  const nodeOps = {
154
182
  createElement(tag) {
@@ -161,6 +189,10 @@ const nodeOps = {
161
189
  const node = createEngineElement(descriptor.component, descriptor.isText, tag);
162
190
  if (descriptor.isText)
163
191
  seedTextDefaults(node);
192
+ // Only the multiline tag is seeded: writing `multiline: false` on the single-line one would add
193
+ // a key the wrapper's payload does not carry, i.e. a divergence in the other direction.
194
+ if (tag === TEXT_INPUT_MULTILINE_TAG)
195
+ setEngineProp(node, MULTILINE_PROP, true);
164
196
  // Graft the imperative public-instance API (measure / setNativeProps / focus / …) onto the raw
165
197
  // node so a `ref` to a host element exposes it exactly like React's getPublicInstance.
166
198
  // toPublicInstance mutates in place and returns the SAME node identity, so the engine's commit
@@ -196,11 +228,15 @@ const nodeOps = {
196
228
  setProperty(node, name, value) {
197
229
  if (isSurface(node))
198
230
  return;
231
+ if (name === MULTILINE_PROP)
232
+ assertMultilineMatchesTag(node, value);
233
+ if (name === ALIAS_TO && aliasedNodes.has(node))
234
+ return;
199
235
  // routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on a View
200
236
  // becomes a listener; onTintColor on a Switch stays a prop), and centralizes the class+style
201
237
  // merge. Shared with React and Vue — never re-implement an `onX` check here
202
238
  // (symbiote-engine-core §2).
203
- routeProp(node, foldAliasKey(name), foldTextValue(node, name, value));
239
+ routeProp(node, foldAliasKey(node, name, value), foldTextValue(node, name, value));
204
240
  requestCommit();
205
241
  },
206
242
  insertNode(parent, node, anchor) {
@@ -232,10 +268,27 @@ const nodeOps = {
232
268
  requestCommit();
233
269
  },
234
270
  removeNode,
271
+ // Reverse of `getFirstChild`'s `childHost` redirect: a child under a composed primitive's slot
272
+ // has `node.parent` pointing at the SLOT, but `cleanChildren`'s `isParent = getParentNode(el)
273
+ // === parent` compares against the OWNER — the same reference the JSX-level `insert()` call was
274
+ // given. Left un-redirected, that comparison is false for every child of a bare `<scroll-view>`
275
+ // (or any other slotted primitive) with a following sibling, so `isParent` never fires,
276
+ // `removeNode` is never called on a full array clear, and the old children are orphaned: still
277
+ // committed to Fabric, still retained, and re-diffed against on the next reconciliation —
278
+ // reproduced as unbounded RAM growth and an increasingly slow "Clear" on the benchmark screen.
279
+ // One level up only: `childHost` is always a DIRECT child the owner built (see `buildStructure`),
280
+ // never a deeper descendant.
235
281
  getParentNode(node) {
236
282
  if (isSurface(node))
237
283
  return undefined;
238
- return node.parent ?? activeSurface;
284
+ const parent = node.parent ?? activeSurface;
285
+ if (parent === undefined || isSurface(parent))
286
+ return parent;
287
+ const grandparent = parent.parent;
288
+ if (grandparent !== undefined && grandparent.childHost === parent) {
289
+ return grandparent;
290
+ }
291
+ return parent;
239
292
  },
240
293
  // Anchors are NOT filtered out of this or getNextSibling, deliberately. solid-js/universal keeps
241
294
  // its own record of which nodes it put where and re-derives positions through these two lookups
@@ -243,8 +296,18 @@ const nodeOps = {
243
296
  // getFirstChild(parent) to replace text in place). Hiding a node the runtime itself inserted
244
297
  // desyncs that record from the real tree. Anchors are invisible to FABRIC — the commit walk skips
245
298
  // them — not to tree traversal.
299
+ //
300
+ // `childHost` redirected, same as the engine's own `appendChild`/`insertBefore`/`removeChild`
301
+ // (`hostFor`, core/engine/src/node.ts): a composed primitive's real children live on the slot it
302
+ // built, not on the owner it was asked to insert into, and `insert()`'s reconciliation reads this
303
+ // to know what is ALREADY there before deciding what to insert/move/remove. Reading the owner's
304
+ // own (single-child) array here would have every dynamic child on a bare `<scroll-view>` diffed
305
+ // against the wrong node — invisible on VirtualizedList, which never calls `insert()` on the
306
+ // owner directly, and wrong for any app writing `<scroll-view>{dynamicChildren}</scroll-view>`.
246
307
  getFirstChild(node) {
247
- return node.children[0];
308
+ if (isSurface(node))
309
+ return node.children[0];
310
+ return (node.childHost ?? node).children[0];
248
311
  },
249
312
  getNextSibling(node) {
250
313
  if (isSurface(node))
@@ -1 +1 @@
1
- export declare function withStableKeys(props: () => Record<string, unknown>): () => Record<string, unknown>;
1
+ export declare function withStableKeys<TProps extends object>(props: () => TProps): () => Record<string, unknown>;
@@ -12,9 +12,10 @@
12
12
  // the actual diffing. Never hand-roll that diff instead.
13
13
  //
14
14
  // Lives here rather than beside either caller because BOTH reach for it: descriptorToSolid wraps
15
- // a render fn's Descriptor props, and the host primitives (View, Text) wrap their own
16
- // resolveAccessibilityProps output — the same function, the same two-branch key set. Any future
17
- // Solid component that folds props through a whole-object transform belongs on this too.
15
+ // a render fn's Descriptor props, and a component that spreads a folded bag onto a bare `<view>`
16
+ // (Pressable, KeyboardAvoidingView) wraps its own — the same function, the same two-branch key
17
+ // set. This is the one capability a bare tag does NOT inherit from the wrappers that used to own
18
+ // it, so any Solid component folding props through a whole-object transform belongs on it too.
18
19
  export function withStableKeys(props) {
19
20
  const seen = new Set();
20
21
  return () => {
@@ -22,9 +23,9 @@ export function withStableKeys(props) {
22
23
  const widened = {};
23
24
  for (const key of seen)
24
25
  widened[key] = undefined;
25
- for (const key of Object.keys(next)) {
26
+ for (const [key, value] of Object.entries(next)) {
26
27
  seen.add(key);
27
- widened[key] = next[key];
28
+ widened[key] = value;
28
29
  }
29
30
  return widened;
30
31
  };
@@ -5,4 +5,5 @@
5
5
  // .cjs, not .js: this package is "type": "module", but Metro's babelTransformerPath loading needs
6
6
  // a require()-able file. createCssMetroTransformer() is a factory — Metro needs the actual
7
7
  // {transform, getCacheKey} object it returns, not the css-parser package barrel.
8
- module.exports = require('@symbiote-native/css-parser').createCssMetroTransformer();
8
+ module.exports =
9
+ require('@symbiote-native/css-parser').createCssMetroTransformer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/solid",
3
- "version": "1.0.0",
3
+ "version": "2.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",
@@ -49,16 +49,11 @@
49
49
  "default": "./build/jsx-runtime.js"
50
50
  },
51
51
  "./babel-preset": "./babel-preset.cjs",
52
- "./metro-css-parser": "./metro-css-parser.cjs",
53
- "./state-style": {
54
- "types": "./build/state-style.d.ts",
55
- "default": "./build/state-style.js"
56
- }
52
+ "./metro-css-parser": "./metro-css-parser.cjs"
57
53
  },
58
54
  "files": [
59
55
  "build",
60
56
  "babel-preset.cjs",
61
- "babel-lower-host-primitives.cjs",
62
57
  "metro-css-parser.cjs"
63
58
  ],
64
59
  "publishConfig": {
@@ -67,18 +62,18 @@
67
62
  "dependencies": {
68
63
  "babel-preset-solid": "^1.9.12",
69
64
  "typescript": "~6.0.0",
70
- "@symbiote-native/components": "1.0.0",
65
+ "@symbiote-native/components": "2.0.0",
71
66
  "@symbiote-native/css-parser": "0.5.0"
72
67
  },
73
68
  "peerDependencies": {
74
69
  "solid-js": ">=1.9.0",
75
70
  "react-native": ">=0.86",
76
- "@symbiote-native/engine": "^0.4.0"
71
+ "@symbiote-native/engine": "^0.5.0"
77
72
  },
78
73
  "devDependencies": {
79
74
  "@babel/core": "^7.25.2",
80
75
  "solid-js": "^1.9.14",
81
- "@symbiote-native/engine": "0.4.0",
76
+ "@symbiote-native/engine": "0.5.0",
82
77
  "@symbiote-native/test-utils": "0.3.0"
83
78
  },
84
79
  "scripts": {