@symbiote-native/solid 0.0.1 → 0.1.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 (105) hide show
  1. package/build/bootstrap.d.ts +7 -0
  2. package/build/bootstrap.js +18 -0
  3. package/build/components/activity-indicator/index.android.d.ts +2 -0
  4. package/build/components/activity-indicator/index.android.js +11 -0
  5. package/build/components/activity-indicator/index.d.ts +1 -0
  6. package/build/components/activity-indicator/index.ios.d.ts +2 -0
  7. package/build/components/activity-indicator/index.ios.js +10 -0
  8. package/build/components/activity-indicator/index.js +4 -0
  9. package/build/components/activity-indicator/shared.d.ts +8 -0
  10. package/build/components/activity-indicator/shared.js +51 -0
  11. package/build/components/button.d.ts +7 -0
  12. package/build/components/button.jsx +45 -0
  13. package/build/components/flat-list/index.d.ts +10 -0
  14. package/build/components/flat-list/index.jsx +118 -0
  15. package/build/components/image-background.d.ts +10 -0
  16. package/build/components/image-background.jsx +74 -0
  17. package/build/components/image.d.ts +9 -0
  18. package/build/components/image.js +75 -0
  19. package/build/components/input-accessory-view.d.ts +11 -0
  20. package/build/components/input-accessory-view.jsx +39 -0
  21. package/build/components/keyboard-avoiding-view.d.ts +15 -0
  22. package/build/components/keyboard-avoiding-view.jsx +152 -0
  23. package/build/components/modal.d.ts +24 -0
  24. package/build/components/modal.jsx +125 -0
  25. package/build/components/pressable.d.ts +30 -0
  26. package/build/components/pressable.jsx +185 -0
  27. package/build/components/refresh-control.d.ts +18 -0
  28. package/build/components/refresh-control.jsx +36 -0
  29. package/build/components/safe-area-view.d.ts +10 -0
  30. package/build/components/safe-area-view.jsx +24 -0
  31. package/build/components/scroll-view/index.android.d.ts +2 -0
  32. package/build/components/scroll-view/index.android.js +11 -0
  33. package/build/components/scroll-view/index.d.ts +2 -0
  34. package/build/components/scroll-view/index.ios.d.ts +2 -0
  35. package/build/components/scroll-view/index.ios.js +8 -0
  36. package/build/components/scroll-view/index.js +9 -0
  37. package/build/components/scroll-view/shared.d.ts +85 -0
  38. package/build/components/scroll-view/shared.jsx +334 -0
  39. package/build/components/scroll-view/sticky-header.d.ts +18 -0
  40. package/build/components/scroll-view/sticky-header.jsx +182 -0
  41. package/build/components/section-list/index.d.ts +6 -0
  42. package/build/components/section-list/index.jsx +8 -0
  43. package/build/components/switch/index.android.d.ts +2 -0
  44. package/build/components/switch/index.android.js +12 -0
  45. package/build/components/switch/index.d.ts +1 -0
  46. package/build/components/switch/index.ios.d.ts +2 -0
  47. package/build/components/switch/index.ios.js +11 -0
  48. package/build/components/switch/index.js +4 -0
  49. package/build/components/switch/shared.d.ts +11 -0
  50. package/build/components/switch/shared.js +87 -0
  51. package/build/components/text-input.d.ts +10 -0
  52. package/build/components/text-input.js +268 -0
  53. package/build/components/text.d.ts +26 -0
  54. package/build/components/text.jsx +27 -0
  55. package/build/components/touchable/index.d.ts +22 -0
  56. package/build/components/touchable/index.jsx +247 -0
  57. package/build/components/touchable-native-feedback/index.d.ts +16 -0
  58. package/build/components/touchable-native-feedback/index.jsx +68 -0
  59. package/build/components/view.d.ts +32 -0
  60. package/build/components/view.jsx +49 -0
  61. package/build/components/virtualized-list/index.android.d.ts +2 -0
  62. package/build/components/virtualized-list/index.android.js +9 -0
  63. package/build/components/virtualized-list/index.d.ts +1 -0
  64. package/build/components/virtualized-list/index.ios.d.ts +2 -0
  65. package/build/components/virtualized-list/index.ios.js +9 -0
  66. package/build/components/virtualized-list/index.js +4 -0
  67. package/build/components/virtualized-list/shared.d.ts +75 -0
  68. package/build/components/virtualized-list/shared.jsx +782 -0
  69. package/build/components/virtualized-section-list/index.d.ts +62 -0
  70. package/build/components/virtualized-section-list/index.jsx +145 -0
  71. package/build/components.d.ts +43 -0
  72. package/build/components.js +54 -0
  73. package/build/create-portal/index.d.ts +11 -0
  74. package/build/create-portal/index.jsx +88 -0
  75. package/build/create-tunnel/index.d.ts +13 -0
  76. package/build/create-tunnel/index.jsx +53 -0
  77. package/build/descriptor-to-solid.d.ts +3 -0
  78. package/build/descriptor-to-solid.js +91 -0
  79. package/build/host-instance.d.ts +2 -0
  80. package/build/host-instance.js +36 -0
  81. package/build/index.d.ts +30 -0
  82. package/build/index.js +96 -0
  83. package/build/jsx-runtime.d.ts +26 -0
  84. package/build/jsx-runtime.js +46 -0
  85. package/build/modules/animated/create-animated-component.d.ts +10 -0
  86. package/build/modules/animated/create-animated-component.jsx +105 -0
  87. package/build/modules/animated/index.d.ts +54 -0
  88. package/build/modules/animated/index.js +78 -0
  89. package/build/modules/app-registry/index.d.ts +7 -0
  90. package/build/modules/app-registry/index.js +37 -0
  91. package/build/modules/status-bar.d.ts +7 -0
  92. package/build/modules/status-bar.js +45 -0
  93. package/build/primitives/create-color-scheme.d.ts +3 -0
  94. package/build/primitives/create-color-scheme.js +33 -0
  95. package/build/primitives/create-window-dimensions.d.ts +3 -0
  96. package/build/primitives/create-window-dimensions.js +34 -0
  97. package/build/render.d.ts +7 -0
  98. package/build/render.js +91 -0
  99. package/build/renderer.d.ts +7 -0
  100. package/build/renderer.js +195 -0
  101. package/build/utils/host-ref.d.ts +3 -0
  102. package/build/utils/host-ref.js +22 -0
  103. package/build/utils/stable-keys.d.ts +1 -0
  104. package/build/utils/stable-keys.js +31 -0
  105. package/package.json +5 -5
@@ -0,0 +1,7 @@
1
+ import type { Component } from 'solid-js';
2
+ import { type IBootstrapHostOptions } from '@symbiote-native/components/bootstrap';
3
+ export type { IBootstrapHostOptions } from '@symbiote-native/components/bootstrap';
4
+ export type ISymbioteSolidApp = {
5
+ mount(appName: string): void;
6
+ };
7
+ export declare function createApp(RootComponent: Component, options?: IBootstrapHostOptions): ISymbioteSolidApp;
@@ -0,0 +1,18 @@
1
+ // Zero-config app entry, the Solid twin of adapters/vue/src/bootstrap.ts and
2
+ // adapters/svelte/src/bootstrap.ts. Lives OUTSIDE the package's main barrel — react-native's own
3
+ // source is Flow syntax Vitest's transform can't parse, so anything importing it directly must stay
4
+ // unreachable from the tested main index.ts.
5
+ import { AppRegistry as RNAppRegistry } from 'react-native';
6
+ import { bootstrapHost, } from '@symbiote-native/components/bootstrap';
7
+ import { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
8
+ // Mirrors the Vue/React/Svelte adapters' createApp(App).mount(appName) two-step idiom: createApp
9
+ // stays inert, and mount(appName) is where the RN host seams and AppRegistry get wired.
10
+ export function createApp(RootComponent, options = {}) {
11
+ return {
12
+ mount(appName) {
13
+ bootstrapHost(options);
14
+ setHostRegistrar(RNAppRegistry);
15
+ AppRegistry.registerComponent(appName, () => RootComponent);
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,2 @@
1
+ export type { IActivityIndicatorProps, IActivityIndicatorSize } from './shared';
2
+ export declare const ActivityIndicator: (props: import("./shared").IActivityIndicatorProps) => import("@symbiote-native/solid/jsx-runtime").JSX.Element;
@@ -0,0 +1,11 @@
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
+ });
@@ -0,0 +1 @@
1
+ export * from './index.ios';
@@ -0,0 +1,2 @@
1
+ export type { IActivityIndicatorProps, IActivityIndicatorSize } from './shared';
2
+ export declare const ActivityIndicator: (props: import("./shared").IActivityIndicatorProps) => import("@symbiote-native/solid/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
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
+ });
@@ -0,0 +1,4 @@
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';
@@ -0,0 +1,8 @@
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;
@@ -0,0 +1,51 @@
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
+ }
@@ -0,0 +1,7 @@
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;
@@ -0,0 +1,45 @@
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
+ }
@@ -0,0 +1,10 @@
1
+ import type { JSX } from '../../jsx-runtime';
2
+ import { type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
3
+ import { type IVirtualizedListHandle, type IVirtualizedListProps } from '../virtualized-list';
4
+ export type IFlatListHandle = IVirtualizedListHandle;
5
+ export type IFlatListProps<ItemT> = Omit<IVirtualizedListProps<ItemT>, 'data' | 'getItem' | 'getItemCount'> & {
6
+ data: readonly ItemT[];
7
+ numColumns?: number;
8
+ columnWrapperStyle?: IStyleProp<IViewStyle> | string;
9
+ };
10
+ export declare function FlatList<ItemT>(props: IFlatListProps<ItemT>): JSX.Element;
@@ -0,0 +1,118 @@
1
+ // FlatList — the convenience surface over VirtualizedList. It takes a plain `data` array and
2
+ // derives the getItem/getItemCount access protocol itself, so a caller never writes them, and
3
+ // `numColumns` packs that many items into each row so the virtualized stream is ROWS rather than
4
+ // items (RN's FlatList). Everything else — windowing, spacers, viewability, batching, sticky
5
+ // headers, pull-to-refresh, the imperative scrolls — is inherited from VirtualizedList; the data
6
+ // shaping and the row/viewability/separator transforms come from @symbiote-native/components and
7
+ // are shared verbatim with React, Vue, Svelte and Angular. This file adds Solid's lifecycle and
8
+ // nothing else.
9
+ //
10
+ // TWO SOLID FACTS SHAPE IT (.claude/rules/solid-descriptor-bridge.md):
11
+ //
12
+ // 1. `renderItem` takes an ACCESSOR, matching this adapter's VirtualizedList and diverging from
13
+ // React's snapshot signature. Solid has no reconciler under the render prop, so a value would
14
+ // freeze the row at its mount-time item.
15
+ // 2. The columns inside a packed row ride an <Index>, not a <For>. That is the exact MIRROR of
16
+ // VirtualizedList's choice and for the same reason read the other way round: a cell key moves
17
+ // between rows as the window slides, so the CELLS are keyed by value; a column is a fixed
18
+ // positional slot inside its row — column 1 of row 3 is always column 1 of row 3 — so keying it
19
+ // by value would destroy and rebuild the whole column subtree every time the data array is
20
+ // replaced with fresh item objects. <Index> keys by position and hands the item down as an
21
+ // accessor, so only the leaf that reads it re-runs.
22
+ //
23
+ // The imperative handle needs no delegation layer here, unlike Vue's and Svelte's. `ref` is an
24
+ // ordinary prop in the passthrough tail, so it lands on the inner VirtualizedList directly and the
25
+ // caller receives that list's own handle — FlatList's handle IS VirtualizedList's (RN says the
26
+ // same).
27
+ import { Index, Show, createMemo, splitProps, untrack, } from 'solid-js';
28
+ import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, rowKeyExtractor, } from '@symbiote-native/components';
29
+ import { dlog, resolveClassName, } from '@symbiote-native/engine';
30
+ import { VirtualizedList, } from '../virtualized-list';
31
+ // What FlatList itself consumes or re-shapes. Everything NOT listed here is passthrough and rides
32
+ // down onto the inner list untouched — `ref` among it, which is why no delegate handle is needed.
33
+ const OWN_PROPS = [
34
+ 'data',
35
+ 'numColumns',
36
+ 'columnWrapperStyle',
37
+ 'renderItem',
38
+ 'keyExtractor',
39
+ 'ItemSeparatorComponent',
40
+ 'onViewableItemsChanged',
41
+ 'viewabilityConfigCallbackPairs',
42
+ ];
43
+ const ROW_STYLE = { flexDirection: 'row' };
44
+ // Equal weight per column, so `numColumns` columns split the row evenly (RN's own multi-column
45
+ // FlatList). A cell that sized itself would leave ragged columns.
46
+ const COLUMN_STYLE = { flex: 1 };
47
+ export function FlatList(props) {
48
+ const [, passthrough] = splitProps(props, OWN_PROPS);
49
+ const columnCount = () => props.numColumns ?? SINGLE_COLUMN;
50
+ // RN treats anything at or below one column as an ordinary single-column list rather than an
51
+ // error, and so does adapters/react.
52
+ const isMultiColumn = () => columnCount() > SINGLE_COLUMN;
53
+ const rows = createMemo(() => {
54
+ const columns = columnCount();
55
+ // Logged from inside the memo, not from the body: a Solid body runs ONCE, so a line emitted
56
+ // there would report the mount-time shape forever and be actively misleading on a data change.
57
+ dlog(`Solid FlatList over ${props.data.length} items, ${columns} column(s)`);
58
+ return columns > SINGLE_COLUMN ? chunkIntoRows(props.data, columns) : [];
59
+ });
60
+ // A bare string is a registered class name and has to be resolved here — the row view's style
61
+ // prop takes objects/arrays, so an unresolved string would silently paint nothing.
62
+ const rowStyle = createMemo(() => [
63
+ ROW_STYLE,
64
+ typeof props.columnWrapperStyle === 'string'
65
+ ? resolveClassName(props.columnWrapperStyle)
66
+ : props.columnWrapperStyle,
67
+ ]);
68
+ function renderRow(info) {
69
+ return (<symbiote-view style={rowStyle()}>
70
+ <Index each={info().item.items}>
71
+ {(item, column) => {
72
+ // Called ONCE and untracked, the shape VirtualizedList's own cell build uses: a
73
+ // tracked call would put every signal renderItem reads into this column's insert
74
+ // effect and rebuild the subtree instead of updating its leaf.
75
+ const content = untrack(() => props.renderItem(() => ({
76
+ item: item(),
77
+ index: info().item.startIndex + column,
78
+ // The row IS the virtualized cell, so every column in it shares the row's
79
+ // separators handle — the divider sits between rows, not between columns.
80
+ separators: info().separators,
81
+ })));
82
+ return (<symbiote-view style={COLUMN_STYLE}>{content}</symbiote-view>);
83
+ }}
84
+ </Index>
85
+ </symbiote-view>);
86
+ }
87
+ // The divider between rows shows real items (last of the row above, first of the row below), so
88
+ // the caller's separator, typed on ItemT, never sees the IRow wrapper.
89
+ const rowSeparatorComponent = createMemo(() => {
90
+ const Separator = props.ItemSeparatorComponent;
91
+ if (Separator === undefined)
92
+ return undefined;
93
+ return (rowProps) => (<Separator {...rowProps} leadingItem={lastItemOfRow(rowProps.leadingItem)} trailingItem={firstItemOfRow(rowProps.trailingItem)}/>);
94
+ });
95
+ // Viewability over rows expands back to per-item tokens, so the caller sees item-level
96
+ // visibility rather than row-level (shared expandRowViewability).
97
+ const rowViewability = createMemo(() => {
98
+ const onChanged = props.onViewableItemsChanged;
99
+ if (onChanged === undefined)
100
+ return undefined;
101
+ return (rowInfo) => onChanged(expandRowViewability(rowInfo, props.keyExtractor));
102
+ });
103
+ const rowViewabilityPairs = createMemo(() => props.viewabilityConfigCallbackPairs?.map(pair => ({
104
+ viewabilityConfig: pair.viewabilityConfig,
105
+ onViewableItemsChanged: (rowInfo) => {
106
+ pair.onViewableItemsChanged(expandRowViewability(rowInfo, props.keyExtractor));
107
+ },
108
+ })));
109
+ // The two paths instantiate VirtualizedList over DIFFERENT item types — ItemT vs IRow<ItemT> —
110
+ // so they are two elements, not one with switching props: a single instantiation would have to
111
+ // union the two and narrow ItemT | IRow<ItemT> back apart at runtime, which nothing can do
112
+ // soundly for an arbitrary ItemT. <Show> memoizes on the flip alone, so a numColumns change
113
+ // rebuilds (RN documents changing it live as unsupported — "change the key prop" — which is the
114
+ // same fresh start) while every other prop change re-props the live list.
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()}/>
117
+ </Show>);
118
+ }
@@ -0,0 +1,10 @@
1
+ import type { JSX } from '../jsx-runtime';
2
+ import { type IImageProps } from '@symbiote-native/components';
3
+ import { type IClassNameValue, type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
4
+ export interface IImageBackgroundProps extends Omit<IImageProps, 'style'> {
5
+ style?: IStyleProp<IViewStyle>;
6
+ imageStyle?: IStyleProp<IViewStyle> | string;
7
+ class?: IClassNameValue;
8
+ children?: JSX.Element;
9
+ }
10
+ export declare function ImageBackground(props: IImageBackgroundProps): JSX.Element;
@@ -0,0 +1,74 @@
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
+ }
@@ -0,0 +1,9 @@
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;
@@ -0,0 +1,75 @@
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);
@@ -0,0 +1,11 @@
1
+ import type { JSX } from '../jsx-runtime';
2
+ import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
3
+ import type { IClassNameValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
4
+ export interface IInputAccessoryViewProps extends IAccessibilityProps, IAriaProps {
5
+ nativeID?: string;
6
+ backgroundColor?: string;
7
+ style?: IStyleProp<IViewStyle>;
8
+ class?: IClassNameValue;
9
+ children?: JSX.Element;
10
+ }
11
+ export declare function InputAccessoryView(props: IInputAccessoryViewProps): JSX.Element;
@@ -0,0 +1,39 @@
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
+ }
@@ -0,0 +1,15 @@
1
+ import type { JSX } from '../jsx-runtime';
2
+ import { type IAccessibilityProps, type IAriaProps, type IKeyboardAvoidingBehavior } from '@symbiote-native/components';
3
+ import { type IClassNameValue, type IStyleProp, type ISymbioteEvent, type IViewStyle } from '@symbiote-native/engine';
4
+ export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
5
+ export interface IKeyboardAvoidingViewProps extends IAccessibilityProps, IAriaProps {
6
+ behavior?: IKeyboardAvoidingBehavior;
7
+ enabled?: boolean;
8
+ keyboardVerticalOffset?: number;
9
+ contentContainerStyle?: IStyleProp<IViewStyle>;
10
+ style?: IStyleProp<IViewStyle>;
11
+ class?: IClassNameValue;
12
+ onLayout?: (event: ISymbioteEvent) => void;
13
+ children?: JSX.Element;
14
+ }
15
+ export declare function KeyboardAvoidingView(props: IKeyboardAvoidingViewProps): JSX.Element;