@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
@@ -1,11 +0,0 @@
1
- // ActivityIndicator on Android: AndroidProgressBar needs `styleAttr` (it drives its setStyle();
2
- // without it the view throws "setStyle() not called") plus `indeterminate: true`, and its default
3
- // color is the theme (null), which the render fn turns into an OMITTED color prop — a literal null
4
- // would be rejected by Fabric's color parser. Same facts the other adapters' Android bindings
5
- // encode. Metro picks this on an Android host; no Platform.OS read.
6
- import { createActivityIndicator } from './shared.js';
7
- export const ActivityIndicator = createActivityIndicator({
8
- // RN: `color = Platform.OS === 'ios' ? GRAY : null`; Android lets the theme color it.
9
- defaultColor: null,
10
- nativeExtras: { styleAttr: 'Normal', indeterminate: true },
11
- });
@@ -1 +0,0 @@
1
- export * from './index.ios';
@@ -1,2 +0,0 @@
1
- export type { IActivityIndicatorProps, IActivityIndicatorSize } from './shared';
2
- export declare const ActivityIndicator: (props: import("./shared").IActivityIndicatorProps) => import("@symbiote-native/solid/jsx-runtime").JSX.Element;
@@ -1,10 +0,0 @@
1
- // ActivityIndicator on iOS: RCTActivityIndicatorView takes the size enum plus a GRAY default color
2
- // and no extra native props — the same facts adapters/react's, adapters/vue's and adapters/svelte's
3
- // iOS bindings encode. Metro selects this file on an iOS host, no Platform.OS read.
4
- import { createActivityIndicator } from './shared.js';
5
- // RN's iOS default spinner color (Libraries/.../ActivityIndicator.js GRAY).
6
- const IOS_DEFAULT_COLOR = '#999999';
7
- export const ActivityIndicator = createActivityIndicator({
8
- defaultColor: IOS_DEFAULT_COLOR,
9
- nativeExtras: {},
10
- });
@@ -1,4 +0,0 @@
1
- // Base / default ActivityIndicator: re-exports the iOS build. Metro overrides this with
2
- // index.ios.ts / index.android.ts on a real host; under vitest / tsc the resolution lands here.
3
- // Filename is the selector, no Platform.OS read.
4
- export * from './index.ios.js';
@@ -1,8 +0,0 @@
1
- import type { JSX } from '../../jsx-runtime';
2
- import { type IActivityIndicatorPlatform, type IActivityIndicatorProps as IActivityIndicatorBaseProps } from '@symbiote-native/components';
3
- import type { IClassNameValue } from '@symbiote-native/engine';
4
- export type { IActivityIndicatorPlatform, IActivityIndicatorSize, } from '@symbiote-native/components';
5
- export type IActivityIndicatorProps = IActivityIndicatorBaseProps & {
6
- class?: IClassNameValue;
7
- };
8
- export declare function createActivityIndicator(platform: IActivityIndicatorPlatform): (props: IActivityIndicatorProps) => JSX.Element;
@@ -1,51 +0,0 @@
1
- // ActivityIndicator, the Solid lifecycle half. The render (the 'small'/'large' -> native size enum
2
- // translation, the fixed size boxes, the centering wrapper, the color omission) lives in
3
- // @symbiote-native/components/view and is shared verbatim with React, Vue and Svelte. There is no
4
- // state machine: the spinner animates natively, so Solid supplies only prop resolution and the
5
- // descriptor bridge.
6
- //
7
- // NOTHING here destructures `props`. Solid props are getters and a component body runs ONCE;
8
- // destructuring reads them at setup and freezes the component at its mount-time values. Every read
9
- // below happens inside the descriptor accessor, which descriptorToSolid re-runs as a render effect
10
- // on the SAME host nodes.
11
- //
12
- // The per-platform bits (iOS's GRAY default and no extras; Android's theme/null default plus
13
- // styleAttr + indeterminate) arrive via `platform` from index.ios.ts / index.android.ts.
14
- import { renderActivityIndicator, resolveAccessibilityProps, } from '@symbiote-native/components';
15
- import { descriptorToSolid } from '../../descriptor-to-solid.js';
16
- // Read by the component itself; everything else (testID, nativeID, the accessibility surface,
17
- // onLayout, class) forwards onto the centering wrapper View, which is where RN spreads `...props`.
18
- const HANDLED_PROPS = [
19
- 'animating',
20
- 'color',
21
- 'hidesWhenStopped',
22
- 'size',
23
- 'style',
24
- ];
25
- // The wrapper is a raw symbiote-view emitted by the render fn, not the View component, so it never
26
- // runs resolveAccessibilityProps itself — the fold happens here, exactly like React's hook does.
27
- // The two branches of that fold emit DIFFERENT key sets; descriptorToSolid runs the resulting bag
28
- // through withStableKeys, so a vanished key still reaches routeProp as a delete
29
- // (.claude/rules/solid-descriptor-bridge.md §1).
30
- function forwardProps(props) {
31
- const forwarded = {
32
- ...resolveAccessibilityProps(props),
33
- };
34
- for (const key of HANDLED_PROPS)
35
- delete forwarded[key];
36
- return forwarded;
37
- }
38
- export function createActivityIndicator(platform) {
39
- return function ActivityIndicator(props) {
40
- // `?? 'small'` / `?? true` rather than a `!== false` coercion: props are typed here, so this is
41
- // the exact semantics of React's destructuring defaults (only `undefined` falls back).
42
- return descriptorToSolid(() => renderActivityIndicator({
43
- animating: props.animating ?? true,
44
- hidesWhenStopped: props.hidesWhenStopped ?? true,
45
- size: props.size ?? 'small',
46
- color: props.color,
47
- style: props.style,
48
- passthrough: forwardProps(props),
49
- }, platform));
50
- };
51
- }
@@ -1,7 +0,0 @@
1
- import type { JSX } from '../jsx-runtime';
2
- import { type IButtonProps as IButtonBaseProps } from '@symbiote-native/components';
3
- import type { IClassNameValue } from '@symbiote-native/engine';
4
- export type IButtonProps = IButtonBaseProps & {
5
- class?: IClassNameValue;
6
- };
7
- export declare function Button(props: IButtonProps): JSX.Element;
@@ -1,45 +0,0 @@
1
- // Button — the minimal cross-platform button, rendered in its iOS shape per RN's Button.js: a
2
- // TouchableOpacity wrapping a Text. The base text style, the role constant and the color fold
3
- // (a caller `color` tints the label; `disabled` greys it, disabled winning) are shared in
4
- // @symbiote-native/components' render-button and are called, never re-derived; Solid only composes
5
- // its own TouchableOpacity + Text and forwards the native-only props.
6
- //
7
- // NO descriptorToSolid here, and none in React/Vue/Svelte either: the tree is two COMPONENTS, not
8
- // a Descriptor. There is no renderButton() to bridge — the shared half is the style/role math.
9
- //
10
- // NOTHING here destructures `props` at setup. Solid props are getters and a component body runs
11
- // ONCE; splitProps keeps both halves reactive and every read sits inside an accessor or a memo.
12
- import { createMemo, splitProps } from 'solid-js';
13
- import { BUTTON_ACCESSIBILITY_ROLE, resolveButtonTextStyle, } from '@symbiote-native/components';
14
- import { Text } from './text.jsx';
15
- import { TouchableOpacity } from './touchable/index.jsx';
16
- export function Button(props) {
17
- // Only what Button CONSUMES or re-maps is split off. Everything else — onPress, disabled,
18
- // testID, the TV-focus props, the accessibility/aria surface, `class` — stays in `rest` and
19
- // rides the single bag below onto the TouchableOpacity. The TV-focus props are real Fabric
20
- // props the pressable forwards without typing; a JSX spread carries them through unchecked
21
- // (they are inert on a phone host).
22
- const [local, rest] = splitProps(props, [
23
- 'title',
24
- 'color',
25
- 'touchSoundDisabled',
26
- ]);
27
- const textStyle = createMemo(() => resolveButtonTextStyle(local.color, rest.disabled));
28
- // ONE bag, not a spread followed by explicit props on the tag: Solid's mergeProps (which the
29
- // compiler uses for that shape) takes the first NON-undefined value scanning back to front, so
30
- // an override that can be undefined silently loses (.claude/rules/solid-descriptor-bridge.md
31
- // §6). A plain object is last-wins — the React semantics this is ported from, and what pins RN's
32
- // fixed role / accessible / disabled-state OVER the caller's own. touchSoundDisabled is RE-MAPPED
33
- // here, not forwarded: the pressable spells it android_disableSound. Return type left INFERRED;
34
- // widening to Record<string, unknown> would stop typing `onPress` too.
35
- const touchableProps = () => ({
36
- ...rest,
37
- android_disableSound: local.touchSoundDisabled,
38
- accessibilityRole: BUTTON_ACCESSIBILITY_ROLE,
39
- accessible: true,
40
- accessibilityState: { disabled: rest.disabled },
41
- });
42
- return (<TouchableOpacity {...touchableProps()}>
43
- <Text style={textStyle()}>{local.title}</Text>
44
- </TouchableOpacity>);
45
- }
@@ -1,74 +0,0 @@
1
- // ImageBackground — the Solid lifecycle half. The composition (the absolute-fill Image behind the
2
- // children, the wrapper-dimension proxy onto that Image, the imageStyle-last merge) lives
3
- // framework-agnostic in @symbiote-native/components' renderImageBackground and is shared verbatim
4
- // with React, Vue and Svelte. Solid supplies only the reactivity, the class-name resolution and
5
- // the child order.
6
- //
7
- // SPLIT DOWN THE MIDDLE, and the seam is the children rule. The wrapper hosts a live Solid subtree
8
- // (the overlay content), which only the reconciler can reduce, so it stays a literal
9
- // `symbiote-view` carrying the shared fn's own props — the same shape View and SafeAreaView use,
10
- // and the same one Svelte's ImageBackground lands on. The inner image is a leaf built entirely
11
- // from VALUES, so it goes through descriptorToSolid, which builds it once and re-props it in place
12
- // (replacing it would restart the download and drop the decoded bitmap).
13
- //
14
- // FLAT FILE, no folder: there are no platform or shared variants to group
15
- // (`symbiote-file-layout` — only genuine groups get a folder).
16
- //
17
- // NOTHING here destructures `props` at setup. Solid props are getters and a component body runs
18
- // ONCE; splitProps keeps every bucket reactive, and each read happens inside an accessor.
19
- import { createMemo, splitProps } from 'solid-js';
20
- import { createDescriptorShapeGuard, renderImageBackground, resolveAccessibilityProps, } from '@symbiote-native/components';
21
- import { resolveClassName, } from '@symbiote-native/engine';
22
- import { descriptorToSolid } from '../descriptor-to-solid.js';
23
- // renderImage's typed view fields — the props it CONSUMES rather than forwards. Split off so they
24
- // never also ride through `passthrough`: the W3C aliases in particular must not reach Fabric raw,
25
- // since native sees only the resolved `source` array they fold into. `style` is absent on purpose
26
- // — the inner image's style is computed by renderImageBackground, never taken from the caller.
27
- const IMAGE_VIEW_PROPS = [
28
- 'source',
29
- 'defaultSource',
30
- 'loadingIndicatorSource',
31
- 'resizeMode',
32
- 'tintColor',
33
- 'src',
34
- 'srcSet',
35
- 'alt',
36
- 'width',
37
- 'height',
38
- 'crossOrigin',
39
- 'referrerPolicy',
40
- ];
41
- const shape = createDescriptorShapeGuard('ImageBackground');
42
- export function ImageBackground(props) {
43
- const [local, view, rest] = splitProps(props, ['children', 'style', 'imageStyle', 'class'], IMAGE_VIEW_PROPS);
44
- // One memo, so the wrapper bag and the inner image derive from a SINGLE call per change.
45
- const wrapper = createMemo(() => renderImageBackground({
46
- style: local.style,
47
- // A bare string is a registered class name and has to become a style object here, before
48
- // it reaches the shared fn — which merges it, and knows nothing about the registry.
49
- imageStyle: typeof local.imageStyle === 'string'
50
- ? resolveClassName(local.imageStyle)
51
- : local.imageStyle,
52
- image: {
53
- ...view,
54
- // The wrapper is a raw host, not the View component, so the aria/role fold is this
55
- // component's own job; the resolved surface rides the IMAGE, matching every other adapter.
56
- passthrough: { ...resolveAccessibilityProps(rest) },
57
- },
58
- }));
59
- const backgroundImage = descriptorToSolid(() => shape.asElement(wrapper().children[0]));
60
- // ONE object literal rather than a spread-then-override on the tag: `class` is legitimately
61
- // undefined, and Solid's mergeProps (which the compiler uses for a spread followed by an
62
- // explicit prop) would discard that undefined instead of applying it
63
- // (.claude/rules/solid-descriptor-bridge.md §6). No withStableKeys — renderImageBackground's
64
- // wrapper props are `{ style }` unconditionally, so no key can vanish between runs.
65
- const wrapperBag = () => ({
66
- ...wrapper().props,
67
- class: local.class,
68
- });
69
- // The user children come AFTER the image in child order, which is what makes them paint on top.
70
- return (<symbiote-view {...wrapperBag()}>
71
- {backgroundImage}
72
- {local.children}
73
- </symbiote-view>);
74
- }
@@ -1,9 +0,0 @@
1
- import type { JSX } from '../jsx-runtime';
2
- import { type IImageProps as IImageBaseProps, type IImageStatics } from '@symbiote-native/components';
3
- import type { IClassNameValue } from '@symbiote-native/engine';
4
- export type { IImageStatics } from '@symbiote-native/components';
5
- export type IImageProps = IImageBaseProps & {
6
- class?: IClassNameValue;
7
- };
8
- export type IImageWithStatics = ((props: IImageProps) => JSX.Element) & IImageStatics;
9
- export declare const Image: IImageWithStatics;
@@ -1,75 +0,0 @@
1
- // Image — the Solid lifecycle half. Everything that turns props into a native prop bag (the
2
- // source / src / srcSet resolution, the width+height fold into style, resizeMode and tintColor
3
- // read back off the style, alt -> accessibilityLabel, the array shape native expects) lives
4
- // framework-agnostic in @symbiote-native/components' renderImage, shared verbatim with React, Vue
5
- // and Svelte. Solid supplies only the reactivity and the Descriptor bridge.
6
- //
7
- // PLAIN .ts, NOT .tsx, unlike ./view.tsx and ./text.tsx — and the difference is the children rule,
8
- // not a style choice. View and Text take a live subtree of the user's components, which only the
9
- // framework's own reconciler can reduce, so they emit real JSX. An image is a leaf built entirely
10
- // from VALUES, so it goes through renderImage + descriptorToSolid exactly like Switch does
11
- // (.claude/rules/component-render-fn-boundary.md).
12
- //
13
- // NOTHING here destructures `props`. Solid props are getters and a component body runs ONCE;
14
- // splitProps is the idiomatic split that keeps both halves reactive, and every read below happens
15
- // inside the descriptor accessor descriptorToSolid re-runs.
16
- //
17
- // ASSET RESOLUTION IS LAZY BY CONSTRUCTION, and that is what lets an app drop the hand-rolled
18
- // resolveImageSource() call it needed while this component did not exist. renderImage calls
19
- // resolveImageSource at RENDER time, and the resolver behind it is installed by bootstrapHost,
20
- // which createApp().mount() runs before AppRegistry ever renders a root. So a `require('./x.png')`
21
- // asset id handed to `source` is resolved by RN's own resolveAssetSource on the way to Fabric,
22
- // with no ordering hazard: a module-scope resolve would run one step too early, an accessor-time
23
- // one cannot.
24
- import { splitProps } from 'solid-js';
25
- import { imageStatics, renderImage, resolveAccessibilityProps, } from '@symbiote-native/components';
26
- import { descriptorToSolid } from '../descriptor-to-solid.js';
27
- // renderImage's typed view fields — the props it CONSUMES rather than forwards. Split off here so
28
- // they never also ride through `passthrough`: the W3C aliases in particular must not reach Fabric
29
- // raw, since native sees only the resolved `source` array they fold into. Everything else (the
30
- // load events, blurRadius, capInsets, resizeMethod, fadeDuration, testID, `class`, a caller's own
31
- // `ref`, the accessibility props) stays in `rest` and lands on the host image untouched.
32
- const VIEW_PROPS = [
33
- 'source',
34
- 'defaultSource',
35
- 'loadingIndicatorSource',
36
- 'style',
37
- 'resizeMode',
38
- 'tintColor',
39
- 'src',
40
- 'srcSet',
41
- 'alt',
42
- 'width',
43
- 'height',
44
- 'crossOrigin',
45
- 'referrerPolicy',
46
- ];
47
- function ImageComponent(props) {
48
- const [view, rest] = splitProps(props, VIEW_PROPS);
49
- // The node is created once and kept by identity — descriptorToSolid wires every prop through a
50
- // render effect on that same node, so a later source/style/alt change re-commits the existing
51
- // image rather than replacing it (which would restart the download and drop the decoded bitmap).
52
- return descriptorToSolid(() => renderImage({
53
- source: view.source,
54
- defaultSource: view.defaultSource,
55
- loadingIndicatorSource: view.loadingIndicatorSource,
56
- style: view.style,
57
- resizeMode: view.resizeMode,
58
- tintColor: view.tintColor,
59
- src: view.src,
60
- srcSet: view.srcSet,
61
- alt: view.alt,
62
- width: view.width,
63
- height: view.height,
64
- crossOrigin: view.crossOrigin,
65
- referrerPolicy: view.referrerPolicy,
66
- // Image owns its host element rather than rendering through a symbiote View, so folding
67
- // aria/role into the canonical accessibility* props is its own job, like Switch's.
68
- // renderImage's own `alt` fold then defers to an explicit accessibilityLabel found here.
69
- passthrough: { ...resolveAccessibilityProps(rest) },
70
- }));
71
- }
72
- // Statics attached like RN's own `Image.getSize` / `prefetch` / `queryCache` / `abortPrefetch` /
73
- // `getSizeWithHeaders` / `resolveAssetSource`, by identity off the same @symbiote-native/engine
74
- // module React, Vue and Svelte attach — never a Solid-side reimplementation.
75
- export const Image = Object.assign(ImageComponent, imageStatics);
@@ -1,39 +0,0 @@
1
- // InputAccessoryView — the Solid lifecycle half (iOS). The host-node assembly (nativeID /
2
- // backgroundColor / style / passthrough forwarding onto `symbiote-input-accessory-view`) lives
3
- // framework-agnostic in @symbiote-native/components' renderInputAccessoryView and is shared
4
- // verbatim with React, Vue and Svelte; Solid supplies only the reactivity and the children.
5
- //
6
- // REAL JSX, NOT descriptorToSolid — the same rule View and SafeAreaView follow. The render fn
7
- // returns a host with ZERO structural children by contract; the toolbar content is a live Solid
8
- // subtree only the reconciler can reduce, so the tag stays literal and the shared fn contributes
9
- // its PROPS. The literal tag is safe because renderInputAccessoryView's `type` is a constant.
10
- //
11
- // NOTHING here destructures `props` at setup. Solid props are getters and a component body runs
12
- // ONCE; splitProps keeps both halves reactive, and every read happens inside the bag accessor
13
- // that `spread` re-runs as a render effect on the SAME host node.
14
- import { splitProps } from 'solid-js';
15
- import { renderInputAccessoryView, resolveAccessibilityProps, } from '@symbiote-native/components';
16
- import { withStableKeys } from '../utils/stable-keys.js';
17
- export function InputAccessoryView(props) {
18
- const [local, rest] = splitProps(props, [
19
- 'children',
20
- 'nativeID',
21
- 'backgroundColor',
22
- 'style',
23
- ]);
24
- // withStableKeys is load-bearing twice over: renderInputAccessoryView emits `nativeID` and
25
- // `backgroundColor` CONDITIONALLY, and resolveAccessibilityProps has two branches with
26
- // different key sets. Solid's `spread` has no removal pass, so a key that vanishes between
27
- // runs would keep its last value on the native view forever (see utils/stable-keys.ts).
28
- const bag = withStableKeys(() => renderInputAccessoryView({
29
- nativeID: local.nativeID,
30
- backgroundColor: local.backgroundColor,
31
- style: local.style,
32
- // This component owns its host element rather than rendering through a View, so folding
33
- // aria/role into the canonical accessibility* props is its own job, like Image's.
34
- passthrough: { ...resolveAccessibilityProps(rest) },
35
- }).props);
36
- return (<symbiote-input-accessory-view {...bag()}>
37
- {local.children}
38
- </symbiote-input-accessory-view>);
39
- }
@@ -1,211 +0,0 @@
1
- // Pressable — the Solid lifecycle half. The press lifecycle (the long-press timer, the
2
- // unstable_pressDelay deferral, the pressRetentionOffset drift test, the suppression flags) lives
3
- // in @symbiote-native/components/state as a pure machine over a runtime + host; the render
4
- // decisions (which responder listeners the View carries, the disabled→accessibilityState fold, the
5
- // ripple prop) in @symbiote-native/components/view. Both are shared verbatim with React, Vue and
6
- // Svelte. Solid supplies only the reactivity: a signal for `pressed`, a setup-scope object for the
7
- // press runtime, a plain variable for the responder node, and one memo that rebuilds the handler
8
- // bag when the caller's config changes.
9
- //
10
- // THE RESPONDER EVENTS ARE NEVER NAMED AS EVENTS HERE. buildPressableListeners returns a flat bag
11
- // (`onPress`, `onStartShouldSetResponder`, `onResponderMove`, …) which rides into View's prop bag
12
- // and reaches the engine through routeProp, which decides prop-vs-event from the node's ViewConfig
13
- // and attaches the responder protocol itself (symbiote-engine-core §2). An adapter-side `onX` check
14
- // would both duplicate that and get the edge cases wrong.
15
- //
16
- // Pressable composes View rather than painting its own host tag, exactly as React's and Vue's do —
17
- // so the aria/role fold, the class+style merge and the vanished-key widening (utils/stable-keys)
18
- // all happen once, in View. That also means there is no renderPressable() Descriptor to bridge:
19
- // `children` is a live Solid subtree, which is the case the render-fn boundary rules OUT of core
20
- // (.claude/rules/component-render-fn-boundary.md).
21
- //
22
- // NOTHING here destructures `props`. Solid props are getters and a component body runs ONCE, so a
23
- // destructure would freeze the Pressable at its mount-time config; every read below sits inside an
24
- // accessor, a memo or an event handler.
25
- import { createEffect, createMemo, onCleanup, createSignal, splitProps, untrack, } from 'solid-js';
26
- import { buildPressableListeners, createPressHandlers, createPressRuntime, disposePressRuntime, noteHoverNoop, resolveDisabledAccessibilityState, rippleProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_MIN_PRESS_DURATION_MS, } from '@symbiote-native/components';
27
- import { dlog, } from '@symbiote-native/engine';
28
- import { setHostPressed } from '../renderer.js';
29
- import { View } from './view.jsx';
30
- // Read by Pressable itself; everything else forwards onto View. The user's press callbacks MUST be
31
- // split off — they are pure JS, and it is the machine's SYNTHESIZED handlers that go on the host.
32
- const HANDLED_PROPS = [
33
- 'onPress',
34
- 'onPressIn',
35
- 'onPressOut',
36
- 'onPressMove',
37
- 'onLongPress',
38
- 'delayLongPress',
39
- 'disabled',
40
- 'cancelable',
41
- 'pressRetentionOffset',
42
- 'unstable_pressDelay',
43
- 'android_ripple',
44
- 'android_disableSound',
45
- 'onHoverIn',
46
- 'onHoverOut',
47
- 'delayHoverIn',
48
- 'delayHoverOut',
49
- 'accessibilityState',
50
- 'style',
51
- 'children',
52
- ];
53
- // `hitSlop` is deliberately NOT in that list: the machine reads it for the retention test AND the
54
- // host needs it to enlarge the touch target, so it forwards through `rest` untouched.
55
- function PressableImpl(props, minPressDuration) {
56
- const [local, rest] = splitProps(props, HANDLED_PROPS);
57
- const [pressed, setPressed] = createSignal(false);
58
- // The mutable press runtime (in-flight timers, suppression flags, measured region). A plain
59
- // setup-scope object, never a signal: the machine mutates it on every event and nothing reads it
60
- // reactively — same shape as Vue's setup-scope runtime and Svelte's plain `const`.
61
- const runtime = createPressRuntime();
62
- // The responder node, held by IDENTITY in a plain variable. A store or a deep proxy would become
63
- // a different key than the one the engine's commit mirror holds, and measure() would silently
64
- // no-op (symbiote-engine-core §3).
65
- let responder = null;
66
- const attachRef = (node) => {
67
- responder = node;
68
- };
69
- // The lifecycle seam the machine fills: flip the reactive `pressed` cell, and expose the
70
- // responder's raw frame-measure. Before the first commit the node has no Fabric tag, so measure()
71
- // no-ops and the machine falls back to its radius test — the same degradation every adapter has,
72
- // and not a whenCommitted case: the measure is triggered by a touch, which cannot arrive before
73
- // the view exists.
74
- const host = {
75
- // Two sinks for one fact, and both are needed. The signal drives the FRAMEWORK-visible half —
76
- // a functional `style`, a render-prop child — which is what forces this element to stay a
77
- // component at all. `setHostPressed` drives the ENGINE-visible half, so `.btn:active` applies
78
- // to a refused Pressable exactly as it would to a lowered `symbiote-pressable`. That is what
79
- // makes a refusal cost the component instance and NOT the pressed styling
80
- // (`.claude/rules/host-primitive-tier.md`).
81
- //
82
- // Before the first commit `responder` is null and the engine call is skipped: nothing is
83
- // painted yet, and a press cannot arrive before the view exists.
84
- setPressed: next => {
85
- setPressed(next);
86
- if (responder !== null)
87
- setHostPressed(responder, next);
88
- },
89
- getMeasureFn: () => {
90
- const node = responder;
91
- if (node === null)
92
- return undefined;
93
- return callback => {
94
- node.measure(callback);
95
- };
96
- },
97
- schedule: (callback, ms) => {
98
- const id = setTimeout(callback, ms);
99
- return () => {
100
- clearTimeout(id);
101
- };
102
- },
103
- now: Date.now,
104
- };
105
- onCleanup(() => {
106
- disposePressRuntime(runtime);
107
- });
108
- // Rebuilt when the caller's config changes (the closures capture live values); the runtime
109
- // persists across rebuilds, so an in-flight timer or drift flag survives one. Deliberately does
110
- // NOT read `pressed` — a press must not churn the handler identities the host node holds.
111
- const handlers = createMemo(() => createPressHandlers({
112
- onPress: local.onPress,
113
- onPressIn: local.onPressIn,
114
- onPressOut: local.onPressOut,
115
- onPressMove: local.onPressMove,
116
- onLongPress: local.onLongPress,
117
- delayLongPress: local.delayLongPress ?? DEFAULT_DELAY_LONG_PRESS_MS,
118
- unstable_pressDelay: local.unstable_pressDelay ?? 0,
119
- minPressDuration,
120
- hitSlop: rest.hitSlop,
121
- pressRetentionOffset: local.pressRetentionOffset,
122
- }, runtime, host));
123
- createEffect(() => {
124
- noteHoverNoop(local.onHoverIn, local.onHoverOut);
125
- });
126
- // Reading `pressed` sits INSIDE the function branch on purpose: a static style must not make the
127
- // prop bag depend on the press signal, or every touch would re-diff and re-commit the whole bag
128
- // for nothing. Unlike the children case below, reading it here is SAFE even in the function form
129
- // — the value feeds viewProps, a prop bag, and a prop bag reaches the host through `spread`,
130
- // which is a per-key diff on the SAME element. Nothing is rebuilt, so nothing can be rebuilt
131
- // mid-gesture.
132
- const pressStyle = () => {
133
- const style = local.style;
134
- if (typeof style !== 'function')
135
- return style;
136
- return style({ pressed: pressed() });
137
- };
138
- const viewProps = createMemo(() => {
139
- const disabled = local.disabled;
140
- const bag = {
141
- ...rest,
142
- style: pressStyle(),
143
- accessibilityState: resolveDisabledAccessibilityState(local.accessibilityState, disabled),
144
- };
145
- // Forwarded under RN's own key; inert on iOS.
146
- if (local.android_disableSound !== undefined) {
147
- bag.android_disableSound = local.android_disableSound;
148
- }
149
- // Last, so the synthesized responder listeners win over anything of the same name in `rest`.
150
- // When disabled the bag is EMPTY — every listener key vanishes, and View's withStableKeys
151
- // widens it back to `undefined`, which routeProp treats as a delete. Without that widening a
152
- // Pressable that became disabled would keep its old listeners on the native view forever
153
- // (.claude/rules/solid-descriptor-bridge.md §1).
154
- Object.assign(bag, buildPressableListeners(handlers(), {
155
- disabled,
156
- cancelable: local.cancelable,
157
- }));
158
- return bag;
159
- });
160
- // A memo, not a bare `() => ({ pressed: pressed() })`: two leaves reading `state()` in the same
161
- // press must see the same object, and an unchanged press must not hand out a fresh one.
162
- const pressState = createMemo(() => ({ pressed: pressed() }));
163
- // WHY AN ACCESSOR AND NOT `{ pressed: pressed() }`. Solid has no reconciler between what this
164
- // file returns and the host nodes — `insert` REPLACES a subtree, it never diffs one (the same
165
- // fact descriptor-to-solid.ts opens with). resolveChildren runs inside View's `insert` render
166
- // effect, so reading `pressed()` there put the press signal in that effect's dependency set:
167
- // every touch tore the child subtree down and built a fresh one. On device that rebuild landed
168
- // between `pressIn` and the native responder grant, the grant was lost, the gesture died, and
169
- // `pressed` stayed true — the "fires on every other tap, label stuck on its pressed text"
170
- // report. Vue diffs the slot's vnodes, Svelte patches a Snippet in place, Angular updates a
171
- // template context; all three reuse the nodes underneath. Solid has no such layer, so the
172
- // reactivity must cross the boundary as an accessor and only the leaf that reads it re-runs.
173
- //
174
- // The call is UNTRACKED and happens once, which is exactly how Solid core treats its own render
175
- // props: `<For>` runs the map fn under createRoot, and createRoot nulls the Listener. A signal
176
- // read at the top level of the child function therefore does not re-run it — nest it in the JSX
177
- // (or a <Show>) like anywhere else in Solid.
178
- const resolveChildren = () => {
179
- const children = local.children;
180
- if (typeof children !== 'function')
181
- return children;
182
- // A zero-argument accessor is an ordinary Solid JSX child (JSX.Element covers `() => Element`),
183
- // not a render prop, and `typeof` cannot tell the two apart — arity can. Handing it back
184
- // unread lets `insert` wrap it in its own render effect, which keeps it reactive; calling it
185
- // here, under untrack, would freeze it at its first value.
186
- if (children.length === 0)
187
- return () => children(pressState);
188
- return untrack(() => children(pressState));
189
- };
190
- // android_ripple rides a dedicated inner View — Android's ReactViewManager reads the ripple
191
- // background off a child, not off the responder itself. rippleProps() returns undefined off
192
- // Android, so the child renders unwrapped, no extra node (RN Pressable.js: "inert on iOS").
193
- const renderContent = () => {
194
- const config = local.android_ripple;
195
- const ripple = config !== undefined ? rippleProps(config) : undefined;
196
- if (ripple === undefined)
197
- return resolveChildren();
198
- dlog('Pressable wrapping children in an android_ripple View');
199
- return <symbiote-view {...ripple}>{resolveChildren()}</symbiote-view>;
200
- };
201
- return (<View ref={attachRef} {...viewProps()}>
202
- {renderContent()}
203
- </View>);
204
- }
205
- export function Pressable(props) {
206
- return PressableImpl(props, DEFAULT_MIN_PRESS_DURATION_MS);
207
- }
208
- // Relative-only composition seam; the public component barrel exports Pressable, never this.
209
- export function TouchablePressable(props) {
210
- return PressableImpl(props, 0);
211
- }
@@ -1,36 +0,0 @@
1
- // RefreshControl — the Solid lifecycle half, and there is barely any: the component owns one host
2
- // tag and forwards its props onto it. On iOS `symbiote-refresh-control` resolves to Fabric's
3
- // PullToRefreshView, which lives INSIDE a ScrollView as a childless sibling before the content
4
- // container; on Android it is AndroidSwipeRefreshLayout and WRAPS the scroll view instead (an
5
- // Android ScrollView hosts exactly one child). Which of the two shapes is built is ScrollView's
6
- // job, not this file's — see scroll-view/shared.tsx's `refreshControlMode`.
7
- //
8
- // `refreshing` is a CONTROLLED prop: the parent owns it and pushes it down, and native reports the
9
- // gesture back through the direct `topRefresh` event. `onRefresh` therefore rides in the forwarded
10
- // bag untouched — routeProp asks the node's ViewConfig whether `onRefresh` is an event and attaches
11
- // it as a listener, which is why nothing here inspects the `on` prefix itself
12
- // (symbiote-engine-core §2).
13
- //
14
- // FLAT, not a folder: the file-layout rule gives a module its own folder only when it has real
15
- // platform (`.ios`/`.android`) or `-shared` variants. There is no JS-side platform branch here —
16
- // every prop forwards to the native node, which reads what it understands and ignores the rest, so
17
- // the Android-only spinner props below are harmless on iOS and vice versa.
18
- import { splitProps } from 'solid-js';
19
- import { resolveAccessibilityProps, } from '@symbiote-native/components';
20
- import { dlog } from '@symbiote-native/engine';
21
- import { withStableKeys } from '../utils/stable-keys.js';
22
- export function RefreshControl(props) {
23
- // `children` is pulled out rather than left in the bag: spread skips it anyway, but leaving it in
24
- // would make the bag accessor re-run whenever the child subtree changed. Same split View makes.
25
- const [local, rest] = splitProps(props, ['children']);
26
- // withStableKeys because resolveAccessibilityProps has two branches with DIFFERENT key sets — it
27
- // returns its input untouched while no aria-* alias holds a value, and a fully-blanked bag once
28
- // one does. Solid's `spread` walks only the current keys and has no removal pass, so a key that
29
- // vanishes would keep its last value on the native view forever
30
- // (.claude/rules/solid-descriptor-bridge.md §1).
31
- const bag = withStableKeys(() => ({ ...resolveAccessibilityProps(rest) }));
32
- dlog('RefreshControl -> PullToRefreshView');
33
- return (<symbiote-refresh-control {...bag()}>
34
- {local.children}
35
- </symbiote-refresh-control>);
36
- }
@@ -1,11 +0,0 @@
1
- import type { JSX } from '../jsx-runtime';
2
- import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
3
- import { type IClassNameValue, type IStyleProp, type ISymbioteEvent, type 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
- }
11
- export declare function SafeAreaView(props: ISafeAreaViewProps): JSX.Element;