@symbiote-native/react 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 (100) hide show
  1. package/README.md +12 -11
  2. package/build/components/activity-indicator-props.d.ts +4 -0
  3. package/build/components/{button.d.ts → button-props.d.ts} +0 -2
  4. package/build/components/button-props.js +1 -0
  5. package/build/components/flat-list/index.js +2 -2
  6. package/build/components/image/image-props.d.ts +4 -0
  7. package/build/components/image/image-props.js +1 -0
  8. package/build/components/image-background-props.d.ts +9 -0
  9. package/build/components/image-background-props.js +1 -0
  10. package/build/components/input-accessory-view-props.d.ts +10 -0
  11. package/build/components/input-accessory-view-props.js +1 -0
  12. package/build/components/keyboard-avoiding-view/index.js +4 -4
  13. package/build/components/modal/index.js +3 -3
  14. package/build/components/pressable/{index.d.ts → pressable-props.d.ts} +4 -5
  15. package/build/components/pressable/pressable-props.js +1 -0
  16. package/build/components/{refresh-control/index.d.ts → refresh-control-props.d.ts} +3 -3
  17. package/build/components/refresh-control-props.js +1 -0
  18. package/build/components/safe-area-view-props.d.ts +11 -0
  19. package/build/components/safe-area-view-props.js +1 -0
  20. package/build/components/scroll-view/{shared.d.ts → scroll-view-props.d.ts} +4 -26
  21. package/build/components/scroll-view/scroll-view-props.js +1 -0
  22. package/build/components/switch/switch-props.d.ts +4 -0
  23. package/build/components/switch/switch-props.js +1 -0
  24. package/build/components/text-input/text-input-props.d.ts +4 -0
  25. package/build/components/text-input/text-input-props.js +1 -0
  26. package/build/components/touchable/touchable-props.d.ts +17 -0
  27. package/build/components/touchable/touchable-props.js +1 -0
  28. package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
  29. package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
  30. package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +6 -0
  31. package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
  32. package/build/components/virtualized-list/index.js +83 -38
  33. package/build/components.d.ts +1 -2
  34. package/build/components.js +5 -19
  35. package/build/create-portal/index.js +1 -1
  36. package/build/create-tunnel/index.js +1 -1
  37. package/build/descriptor-to-react/index.js +1 -1
  38. package/build/host-config.js +22 -4
  39. package/build/index.d.ts +23 -28
  40. package/build/index.js +14 -14
  41. package/build/jsx-dev-runtime.d.ts +3 -0
  42. package/build/jsx-dev-runtime.js +10 -0
  43. package/build/jsx-namespace.type-check.d.ts +1 -0
  44. package/build/jsx-namespace.type-check.js +7 -0
  45. package/build/jsx-runtime.d.ts +62 -0
  46. package/build/jsx-runtime.js +1 -0
  47. package/build/modules/animated/index.d.ts +0 -1
  48. package/build/modules/animated/index.js +16 -22
  49. package/build/modules/image/index.d.ts +3 -0
  50. package/build/modules/image/index.js +14 -0
  51. package/build/register.d.ts +1 -0
  52. package/build/register.js +15 -0
  53. package/metro-css-parser.cjs +2 -1
  54. package/package.json +12 -8
  55. package/build/components/activity-indicator/index.android.d.ts +0 -3
  56. package/build/components/activity-indicator/index.android.js +0 -17
  57. package/build/components/activity-indicator/index.d.ts +0 -1
  58. package/build/components/activity-indicator/index.ios.d.ts +0 -3
  59. package/build/components/activity-indicator/index.ios.js +0 -16
  60. package/build/components/activity-indicator/index.js +0 -4
  61. package/build/components/activity-indicator/shared.d.ts +0 -6
  62. package/build/components/activity-indicator/shared.js +0 -22
  63. package/build/components/button.js +0 -42
  64. package/build/components/image/index.d.ts +0 -9
  65. package/build/components/image/index.js +0 -28
  66. package/build/components/image-background/index.d.ts +0 -10
  67. package/build/components/image-background/index.js +0 -46
  68. package/build/components/input-accessory-view/index.d.ts +0 -11
  69. package/build/components/input-accessory-view/index.js +0 -19
  70. package/build/components/pressable/index.js +0 -112
  71. package/build/components/refresh-control/index.js +0 -21
  72. package/build/components/safe-area-view/index.d.ts +0 -12
  73. package/build/components/safe-area-view/index.js +0 -18
  74. package/build/components/scroll-view/index.android.d.ts +0 -3
  75. package/build/components/scroll-view/index.android.js +0 -61
  76. package/build/components/scroll-view/index.d.ts +0 -1
  77. package/build/components/scroll-view/index.ios.d.ts +0 -3
  78. package/build/components/scroll-view/index.ios.js +0 -29
  79. package/build/components/scroll-view/index.js +0 -4
  80. package/build/components/scroll-view/shared.js +0 -240
  81. package/build/components/scroll-view/sticky-header.d.ts +0 -18
  82. package/build/components/scroll-view/sticky-header.js +0 -227
  83. package/build/components/switch/index.android.d.ts +0 -3
  84. package/build/components/switch/index.android.js +0 -18
  85. package/build/components/switch/index.d.ts +0 -1
  86. package/build/components/switch/index.ios.d.ts +0 -3
  87. package/build/components/switch/index.ios.js +0 -16
  88. package/build/components/switch/index.js +0 -6
  89. package/build/components/switch/shared.d.ts +0 -9
  90. package/build/components/switch/shared.js +0 -60
  91. package/build/components/text-input/index.d.ts +0 -50
  92. package/build/components/text-input/index.js +0 -180
  93. package/build/components/touchable/index.d.ts +0 -22
  94. package/build/components/touchable/index.js +0 -165
  95. package/build/components/touchable-native-feedback/index.d.ts +0 -18
  96. package/build/components/touchable-native-feedback/index.js +0 -32
  97. package/build/jsx.d.ts +0 -18
  98. package/build/state-style.d.ts +0 -1
  99. package/build/state-style.js +0 -13
  100. /package/build/{jsx.js → components/activity-indicator-props.js} +0 -0
package/README.md CHANGED
@@ -35,21 +35,20 @@ yet, so the Metro config and the `index.js` entry seam below come from
35
35
 
36
36
  ## Use it
37
37
 
38
- The app is ordinary React — it imports primitives from `@symbiote-native/react`, not `react-native`:
38
+ The app is ordinary React — the native primitives are plain intrinsic tags, no import needed:
39
39
 
40
40
  ```jsx
41
41
  import { useState } from 'react';
42
- import { View, Text, Pressable } from '@symbiote-native/react';
43
42
 
44
43
  export default function App() {
45
44
  const [count, setCount] = useState(0);
46
45
  return (
47
- <View style={{ padding: 24 }}>
48
- <Text>Taps: {count}</Text>
49
- <Pressable onPress={() => setCount(c => c + 1)}>
50
- <Text>Tap me</Text>
51
- </Pressable>
52
- </View>
46
+ <view style={{ padding: 24 }}>
47
+ <text>Taps: {count}</text>
48
+ <pressable onPress={() => setCount(c => c + 1)}>
49
+ <text>Tap me</text>
50
+ </pressable>
51
+ </view>
53
52
  );
54
53
  }
55
54
  ```
@@ -102,9 +101,11 @@ the surface below.
102
101
  Every interaction commits through `@symbiote-native/engine` into Fabric, with RN's renderer never in the
103
102
  path:
104
103
 
105
- - **Primitives** — `View` · `Text` · `Image` · `ImageBackground` · `ScrollView` · `TextInput` ·
106
- `Pressable` · `Touchable*` · `Button` · `Switch` · `Modal` · `ActivityIndicator` ·
107
- `SafeAreaView` · `RefreshControl` · `FlatList` · `SectionList` · `VirtualizedList`.
104
+ - **Primitives** — `view` · `text` · `image` · `image-background` · `scroll-view` · `text-input` ·
105
+ `pressable` · `touchable-opacity` / `touchable-native-feedback` / `touchable-without-feedback` /
106
+ `touchable-highlight` · `button` · `switch` · `modal` · `activity-indicator` ·
107
+ `safe-area-view` · `refresh-control` · plus the components `FlatList` · `SectionList` ·
108
+ `VirtualizedList`.
108
109
  - **Runtime modules** — `Platform` · `StyleSheet` (incl. `hairlineWidth`) · `Dimensions` ·
109
110
  `Appearance` · `PixelRatio` · `AppState`, plus imperative `Alert` · `ActionSheetIOS` · `Share` ·
110
111
  `Linking` · `Vibration` · `Keyboard` · `StatusBar` — each reaching its real native module on the
@@ -0,0 +1,4 @@
1
+ import type { IActivityIndicatorProps as IActivityIndicatorBaseProps } from '@symbiote-native/components';
2
+ export type IActivityIndicatorProps = IActivityIndicatorBaseProps & {
3
+ className?: string;
4
+ };
@@ -1,6 +1,4 @@
1
- import { type FC } from 'react';
2
1
  import type { IButtonProps as IButtonBaseProps } from '@symbiote-native/components';
3
2
  export type IButtonProps = IButtonBaseProps & {
4
3
  className?: string;
5
4
  };
6
- export declare const Button: FC<IButtonProps>;
@@ -0,0 +1 @@
1
+ export {};
@@ -48,9 +48,9 @@ export function FlatList(props) {
48
48
  const key = keyExtractor ? keyExtractor(item, index) : String(index);
49
49
  // The row IS the virtualized cell, so every item in it shares the row's separators handle
50
50
  // (the divider sits between rows, not columns), like RN's multi-column FlatList.
51
- return createElement('symbiote-view', { key, style: { flex: 1 } }, renderItem({ item, index, separators: info.separators }));
51
+ return createElement('view', { key, style: { flex: 1 } }, renderItem({ item, index, separators: info.separators }));
52
52
  });
53
- return createElement('symbiote-view', { style: rowStyle }, ...cells);
53
+ return createElement('view', { style: rowStyle }, ...cells);
54
54
  };
55
55
  // Viewability over rows expands back to per-item tokens so the caller sees item-level
56
56
  // visibility, not row-level (shared expandRowViewability).
@@ -0,0 +1,4 @@
1
+ import type { IImageProps as IImageBaseProps } from '@symbiote-native/components';
2
+ export type IImageProps = IImageBaseProps & {
3
+ className?: string;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { IImageProps } from '@symbiote-native/components';
3
+ import type { IStyleProp, IViewStyle } from '../utils/styles';
4
+ export interface IImageBackgroundProps extends Omit<IImageProps, 'style'> {
5
+ style?: IStyleProp<IViewStyle>;
6
+ imageStyle?: IStyleProp<IViewStyle> | string;
7
+ className?: string;
8
+ children?: ReactNode;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
3
+ import type { IStyleProp, IViewStyle } from '../utils/styles';
4
+ export interface IInputAccessoryViewProps extends IAccessibilityProps, IAriaProps {
5
+ nativeID?: string;
6
+ backgroundColor?: string;
7
+ style?: IStyleProp<IViewStyle>;
8
+ className?: string;
9
+ children?: ReactNode;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -11,7 +11,7 @@
11
11
  import { createElement, useEffect, useRef, useState, } from 'react';
12
12
  import { dlog, Keyboard, Platform, } from '@symbiote-native/engine';
13
13
  import { computeInset, keyboardAvoidingEventNamesFor, readKeyboardFrame, readLayoutFrame, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
14
- import { View } from '../../components.js';
14
+ import {} from '../../components.js';
15
15
  // The two notifications to subscribe to, per host (iOS takes the `will` pair so the view rides
16
16
  // up with the keyboard animation; Android has no will-notifications). Resolved ONCE at module
17
17
  // scope: Platform.OS is fixed for the process, so recomputing it per render buys nothing.
@@ -86,11 +86,11 @@ export const KeyboardAvoidingView = props => {
86
86
  // 'nested' ('position') pushes the content in an inner View by `bottom: inset`; the wrapper
87
87
  // modes adjust the single wrapper directly.
88
88
  if (layout.kind === 'nested') {
89
- return renderWrapper(layout.wrapperStyle, createElement(View, { style: layout.innerStyle }, children));
89
+ return renderWrapper(layout.wrapperStyle, createElement('view', { style: layout.innerStyle }, children));
90
90
  }
91
91
  return renderWrapper(layout.wrapperStyle, children);
92
92
  // The wrapper carries onLayout. The View FC's public props don't surface it, but
93
- // `symbiote-view` routes the base layout event at runtime; widen the props through
93
+ // `view` routes the base layout event at runtime; widen the props through
94
94
  // a typed variable (no inline-literal excess-property check, no `as`) so the
95
95
  // onLayout reaches the host without editing View's public type.
96
96
  function renderWrapper(wrapStyle, content) {
@@ -100,6 +100,6 @@ export const KeyboardAvoidingView = props => {
100
100
  onLayout: handleLayout,
101
101
  children: content,
102
102
  };
103
- return createElement(View, wrapperProps);
103
+ return createElement('view', wrapperProps);
104
104
  }
105
105
  };
@@ -15,11 +15,11 @@ import { createElement, useEffect, useReducer, } from 'react';
15
15
  import { dlog } from '@symbiote-native/engine';
16
16
  import { createInitialModalState, modalReducer, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
17
17
  export const Modal = rawProps => {
18
- // Modal owns its host element (symbiote-modal), so it folds aria/role here; the resolved fields
18
+ // Modal owns its host element (modal), so it folds aria/role here; the resolved fields
19
19
  // ride the host node via `...passthrough`. The events (onShow/onDismiss/onRequestClose/
20
20
  // onOrientationChange) are real ViewConfig DirectEvents, so they too ride passthrough raw.
21
21
  // className is pulled out here, like style, and applied to the CONTAINER element below — left in
22
- // ...passthrough it would land on the outer symbiote-modal host instead (renderModal composes
22
+ // ...passthrough it would land on the outer modal host instead (renderModal composes
23
23
  // `style` into the container's style, not the host's).
24
24
  const { visible, transparent, backdropColor, animationType, presentationStyle, supportedOrientations, hardwareAccelerated, statusBarTranslucent, navigationBarTranslucent, allowSwipeDismissal, style, className, children, ...passthrough } = resolveAccessibilityProps(rawProps);
25
25
  // The iOS keep-alive: the effect runs AFTER this render, so a visible→hidden transition keeps the
@@ -47,7 +47,7 @@ export const Modal = rawProps => {
47
47
  style,
48
48
  passthrough,
49
49
  });
50
- // root = symbiote-modal > [container]; the user children nest UNDER the container View, never as
50
+ // root = modal > [container]; the user children nest UNDER the container View, never as
51
51
  // a direct sibling of the host (RN's modal content layout).
52
52
  const [container] = root.children;
53
53
  if (typeof container === 'string')
@@ -1,6 +1,5 @@
1
- import { type FC, type ReactNode } from 'react';
2
- import { type IPressState, type IPressHandler, type IRectOffset, type IPressableAndroidRippleConfig } from '@symbiote-native/components';
3
- import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
1
+ import type { ReactNode } from 'react';
2
+ import type { IAccessibilityProps, IAriaProps, IPressHandler, IPressState, IPressableAndroidRippleConfig, IRectOffset } from '@symbiote-native/components';
4
3
  import type { IStyleProp, IViewStyle } from '../../utils/styles';
5
4
  export type { IPressState, IPressableAndroidRippleConfig, } from '@symbiote-native/components';
6
5
  type IPressableStyle = IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
@@ -13,10 +12,12 @@ export interface IPressableProps extends IAccessibilityProps, IAriaProps {
13
12
  onLongPress?: IPressHandler;
14
13
  delayLongPress?: number;
15
14
  disabled?: boolean;
15
+ focusable?: boolean;
16
16
  cancelable?: boolean;
17
17
  hitSlop?: IRectOffset;
18
18
  pressRetentionOffset?: IRectOffset;
19
19
  unstable_pressDelay?: number;
20
+ minPressDuration?: number;
20
21
  android_ripple?: IPressableAndroidRippleConfig;
21
22
  android_disableSound?: boolean;
22
23
  onHoverIn?: IPressHandler;
@@ -27,5 +28,3 @@ export interface IPressableProps extends IAccessibilityProps, IAriaProps {
27
28
  className?: string;
28
29
  children?: IChildrenProp;
29
30
  }
30
- export declare const Pressable: FC<IPressableProps>;
31
- export declare const TouchablePressable: FC<IPressableProps>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,8 @@
1
- import { type FC, type ReactNode } from 'react';
2
- import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
1
+ import type { ReactNode } from 'react';
2
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
3
3
  export interface IRefreshControlProps extends IAccessibilityProps, IAriaProps {
4
4
  refreshing: boolean;
5
+ id?: string;
5
6
  onRefresh?: () => void | Promise<void>;
6
7
  tintColor?: string;
7
8
  title?: string;
@@ -14,4 +15,3 @@ export interface IRefreshControlProps extends IAccessibilityProps, IAriaProps {
14
15
  children?: ReactNode;
15
16
  className?: string;
16
17
  }
17
- export declare const RefreshControl: FC<IRefreshControlProps>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ISymbioteEvent } from '@symbiote-native/engine';
3
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
4
+ import type { IStyleProp, IViewStyle } from '../utils/styles';
5
+ export interface ISafeAreaViewProps extends IAccessibilityProps, IAriaProps {
6
+ style?: IStyleProp<IViewStyle>;
7
+ id?: string;
8
+ className?: string;
9
+ children?: ReactNode;
10
+ onLayout?: (event: ISymbioteEvent) => void;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,9 @@
1
- import { type ReactElement, type ReactNode, type RefObject } from 'react';
2
- import { AnimatedValue, type ISymbioteEvent, type ISymbioteNode } from '@symbiote-native/engine';
3
- import { type ISymbioteIntrinsic } from '@symbiote-native/components';
4
- import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
1
+ import type { ReactNode } from 'react';
2
+ import type { ISymbioteEvent } from '@symbiote-native/engine';
3
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
5
4
  import type { IStyleProp, IViewStyle } from '../../utils/styles';
6
- import { type IStickyHeaderComponentType } from './sticky-header';
7
5
  export type { IScrollViewHandle } from '@symbiote-native/components';
8
6
  type IScrollHandler = (event: ISymbioteEvent) => void;
9
- type ILayoutHandler = (event: ISymbioteEvent) => void;
10
7
  export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
11
8
  style?: IStyleProp<IViewStyle>;
12
9
  contentContainerStyle?: IStyleProp<IViewStyle> | string;
@@ -28,7 +25,6 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
28
25
  x: number;
29
26
  y: number;
30
27
  };
31
- refreshControl?: ReactElement<IClonableRefreshControl>;
32
28
  removeClippedSubviews?: boolean;
33
29
  onContentSizeChange?: (width: number, height: number) => void;
34
30
  snapToInterval?: number;
@@ -39,7 +35,6 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
39
35
  disableIntervalMomentum?: boolean;
40
36
  stickyHeaderIndices?: number[];
41
37
  invertStickyHeaders?: boolean;
42
- StickyHeaderComponent?: IStickyHeaderComponentType;
43
38
  keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
44
39
  keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
45
40
  maintainVisibleContentPosition?: {
@@ -69,7 +64,7 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
69
64
  fadingEdgeLength?: number;
70
65
  persistentScrollbar?: boolean;
71
66
  endFillColor?: string;
72
- onLayout?: ILayoutHandler;
67
+ onLayout?: IScrollHandler;
73
68
  onScroll?: IScrollHandler;
74
69
  onScrollBeginDrag?: IScrollHandler;
75
70
  onScrollEndDrag?: IScrollHandler;
@@ -79,20 +74,3 @@ export interface IScrollViewProps extends IAccessibilityProps, IAriaProps {
79
74
  children?: ReactNode;
80
75
  className?: string;
81
76
  }
82
- export interface IClonableRefreshControl {
83
- style?: IStyleProp<IViewStyle>;
84
- children?: ReactNode;
85
- }
86
- export interface IPreparedScrollView {
87
- scrollViewIntrinsic: ISymbioteIntrinsic;
88
- scrollViewBaseStyle: IStyleProp<IViewStyle> | undefined;
89
- outerProps: Record<string, unknown>;
90
- style: IStyleProp<IViewStyle> | undefined;
91
- layoutSplitStyle: IStyleProp<IViewStyle>;
92
- content: ReactElement;
93
- refreshControl: ReactElement<IClonableRefreshControl> | undefined;
94
- scrollAnimatedValue: AnimatedValue;
95
- nativeStickyAvailable: boolean;
96
- }
97
- export declare function usePreparedScrollView(rawProps: IScrollViewProps): IPreparedScrollView;
98
- export declare function useNativeStickyScrollAttach(scrollNodeRef: RefObject<ISymbioteNode | null>, scrollAnimatedValue: AnimatedValue, enabled: boolean): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { ISwitchProps as ISwitchBaseProps } from '@symbiote-native/components';
2
+ export type ISwitchProps = ISwitchBaseProps & {
3
+ className?: string;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { ITextInputProps as ITextInputBaseProps } from '@symbiote-native/components';
2
+ export type ITextInputProps = ITextInputBaseProps & {
3
+ className?: string;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { IPressTimingProps } from '@symbiote-native/components';
3
+ import type { IStyleProp, IViewStyle } from '../../utils/styles';
4
+ import type { IPressableProps } from '../pressable/pressable-props';
5
+ export type ITouchableBaseProps = Omit<IPressableProps, 'style' | 'children'> & IPressTimingProps & {
6
+ style?: IStyleProp<IViewStyle>;
7
+ children?: ReactNode;
8
+ };
9
+ export interface ITouchableOpacityProps extends ITouchableBaseProps {
10
+ activeOpacity?: number;
11
+ }
12
+ export interface ITouchableHighlightProps extends ITouchableBaseProps {
13
+ activeOpacity?: number;
14
+ underlayColor?: string;
15
+ onShowUnderlay?: () => void;
16
+ onHideUnderlay?: () => void;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { INativeFeedbackBackground } from '@symbiote-native/components';
3
+ import type { IPressableProps } from '../pressable/pressable-props';
4
+ export type ITouchableNativeFeedbackProps = Omit<IPressableProps, 'style' | 'children'> & {
5
+ background?: INativeFeedbackBackground;
6
+ useForeground?: boolean;
7
+ children?: ReactNode;
8
+ };
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { IPressTimingProps } from '@symbiote-native/components';
3
+ import type { IPressableProps } from '../pressable/pressable-props';
4
+ export type ITouchableWithoutFeedbackProps = Omit<IPressableProps, 'style' | 'children'> & IPressTimingProps & {
5
+ children?: ReactNode;
6
+ };
@@ -1,4 +1,4 @@
1
- // VirtualizedList: real windowing over the existing ScrollView. Only the cells
1
+ // VirtualizedList: real windowing over the `scroll-view` TAG. Only the cells
2
2
  // whose computed offset falls inside the visible window (plus a leading/trailing
3
3
  // buffer) are rendered; everything above and below is collapsed into two spacer
4
4
  // Views whose sizes sum to the off-screen extent, so the scroll thumb and total
@@ -14,14 +14,31 @@
14
14
  // from a `commit` dispatched in a layout effect (before paint, so MVCP's shift lands without a
15
15
  // visible jump). Lists have no Descriptor render fn — the cell content is React's own children.
16
16
  //
17
- // Imperative scrolling resolves to an offset in the reducer and rides the ScrollView's native
18
- // scrollTo command via its handle ref, animated by default. The `contentOffset` prop is only a
19
- // fallback for the pre-mount window (handle not yet attached).
17
+ // Imperative scrolling resolves to an offset in the reducer and rides the scroll node's native
18
+ // scrollTo command, animated by default. The `contentOffset` prop is only a fallback for the
19
+ // pre-mount window (the node not yet attached).
20
+ //
21
+ // WHAT IS STILL THIS COMPONENT'S, now that the scroll view is a tag and the engine owns its
22
+ // content node, its sticky seam and its RefreshControl placement — checked rather than assumed,
23
+ // because "the wrapper is gone" and "nothing is left" are different claims:
24
+ //
25
+ // the WINDOW `reduceList` decides which indices are in view, and this file is what
26
+ // turns that into elements. The output SHAPE is decided in JS from the
27
+ // app's own data through `renderItem` — the one disqualifier that no
28
+ // engine seam removes (`.claude/rules/host-primitive-tier.md`, tier 3).
29
+ // the CELL WRAPPERS a measuring `view` per cell with the inversion counter-flip, the two
30
+ // spacers, the header/footer/empty slots, the separator INSIDE the cell.
31
+ // the LIFECYCLE native events -> actions, effects run with React primitives (timers, a
32
+ // forced re-render, a layout effect for MVCP), one state cell.
33
+ // the HANDLE `buildScrollViewHandle` over the node a `ref` hands back, plus the list's
34
+ // own imperative surface on top of it.
35
+ //
36
+ // What is NOT: the content node, `contentContainerStyle`'s routing, where a RefreshControl goes
37
+ // per platform, and how a sticky header pins. The list names `sticky-header` on a cell and knows
38
+ // nothing else about it.
20
39
  import { createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, } from 'react';
21
40
  import { dlog, } from '@symbiote-native/engine';
22
- import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, buildListPlan, buildViewabilityPairs, createInitialListState, isSeparatorGapInRange, listEffectSignature, readLayoutLength, readLayoutOffset, readScrollOffset, reduceList, resolveItemKey, } from '@symbiote-native/components';
23
- import { ScrollView, } from '../scroll-view';
24
- import { RefreshControl } from '../refresh-control/index.js';
41
+ import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, buildListPlan, buildScrollViewHandle, buildViewabilityPairs, createInitialListState, isSeparatorGapInRange, listEffectSignature, readLayoutLength, readLayoutOffset, readScrollOffset, reduceList, resolveItemKey, } from '@symbiote-native/components';
25
42
  function resolveElement(component) {
26
43
  if (component === undefined)
27
44
  return undefined;
@@ -101,7 +118,12 @@ export function VirtualizedList(props) {
101
118
  // The offset we are imperatively driving native to (scrollTo* before the handle attaches). Pushed
102
119
  // down as the ScrollView's contentOffset prop; fresh object identity each time. undefined = none.
103
120
  const [commandedOffset, setCommandedOffset] = useState(undefined);
104
- const scrollViewRef = useRef(null);
121
+ // The engine node of the scroll TAG. A `ref` on an intrinsic hands back the host instance, so
122
+ // there is no wrapper left to run a `useImperativeHandle` — `buildScrollViewHandle` is what turns
123
+ // the node into RN's scroll surface, and it is the same function every other adapter feeds from
124
+ // its own host-instance accessor. Lazy getter: the node is null until commit.
125
+ const scrollNodeRef = useRef(null);
126
+ const scrollHandle = useMemo(() => buildScrollViewHandle(() => scrollNodeRef.current), []);
105
127
  // Pending minimumViewTime debounce timer / the incremental-fill timer (adapter owns the timers;
106
128
  // the reducer only asks for a delay).
107
129
  const viewableTimerRef = useRef(null);
@@ -117,9 +139,9 @@ export function VirtualizedList(props) {
117
139
  const target = isHorizontal
118
140
  ? { x: clamped, y: EMPTY_OFFSET }
119
141
  : { x: EMPTY_OFFSET, y: clamped };
120
- if (scrollViewRef.current !== null) {
142
+ if (scrollNodeRef.current !== null) {
121
143
  dlog(`VirtualizedList scrollTo offset=${clamped} animated=${animated} (horizontal=${isHorizontal})`);
122
- scrollViewRef.current.scrollTo({ x: target.x, y: target.y, animated });
144
+ scrollHandle.scrollTo({ x: target.x, y: target.y, animated });
123
145
  return;
124
146
  }
125
147
  dlog(`VirtualizedList scrollTo offset=${clamped} pending-ref (horizontal=${isHorizontal})`);
@@ -289,18 +311,20 @@ export function VirtualizedList(props) {
289
311
  dispatch({ kind: 'scroll-to-end', animated: params?.animated ?? true });
290
312
  },
291
313
  flashScrollIndicators: () => {
292
- scrollViewRef.current?.flashScrollIndicators?.();
314
+ scrollHandle.flashScrollIndicators?.();
293
315
  },
294
- getNativeScrollRef: () => scrollViewRef.current,
295
- getScrollableNode: () => scrollViewRef.current,
296
- getScrollResponder: () => scrollViewRef.current,
297
- getScrollNode: () => scrollViewRef.current?.getScrollNode() ?? null,
316
+ // Null until the tag commits, so these keep RN's "no scroll view yet" answer rather than
317
+ // handing back a handle that would silently no-op.
318
+ getNativeScrollRef: () => scrollNodeRef.current !== null ? scrollHandle : null,
319
+ getScrollableNode: () => scrollNodeRef.current !== null ? scrollHandle : null,
320
+ getScrollResponder: () => scrollNodeRef.current !== null ? scrollHandle : null,
321
+ getScrollNode: () => scrollNodeRef.current,
298
322
  // Manual trigger for RN's recordInteraction: flip the interaction flag so
299
323
  // waitForInteraction viewability configs start reporting.
300
324
  recordInteraction: () => {
301
325
  dispatch({ kind: 'record-interaction' });
302
326
  },
303
- }), [dispatch]);
327
+ }), [dispatch, scrollHandle]);
304
328
  // After-commit pass: run the deferred effects (batch fill, edge-reached, viewability,
305
329
  // initial-scroll, MVCP) in a LAYOUT effect so MVCP's shift lands before paint. Re-runs only when
306
330
  // the windowing signature changed — the same dedup key every adapter shares.
@@ -328,15 +352,14 @@ export function VirtualizedList(props) {
328
352
  const stickySet = stickyHeaderIndices !== undefined
329
353
  ? new Set(stickyHeaderIndices)
330
354
  : undefined;
331
- let renderedStickyIndices = [];
332
355
  const header = resolveElement(ListHeaderComponent);
333
356
  if (header !== undefined) {
334
- children.push(createElement('symbiote-view', { key: 'list-header' }, header));
357
+ children.push(createElement('view', { key: 'list-header' }, header));
335
358
  }
336
359
  if (count === FIRST_INDEX) {
337
360
  const empty = resolveElement(ListEmptyComponent);
338
361
  if (empty !== undefined) {
339
- children.push(createElement('symbiote-view', { key: 'list-empty' }, empty));
362
+ children.push(createElement('view', { key: 'list-empty' }, empty));
340
363
  }
341
364
  }
342
365
  else {
@@ -352,9 +375,14 @@ export function VirtualizedList(props) {
352
375
  stickyIndices: stickySet,
353
376
  hasHeader: header !== undefined,
354
377
  });
355
- renderedStickyIndices = plan.stickyChildPositions;
378
+ // `plan.stickyChildPositions` is deliberately NOT read. It was the input to the wrapper's own
379
+ // JS sticky pass, and the behavior honours `stickyHeaderIndices` by numbering the OWNER's paint
380
+ // children — which a windowed list cannot supply: it paints a header, a spacer and a slice, so
381
+ // data index 3 is almost never paint child 3, and the positions would have to be recomputed
382
+ // every time the window slides. The cells that should pin carry the `sticky-header` TAG
383
+ // instead, which pins by document order and survives windowing.
356
384
  if (plan.leadingExtent > EMPTY_OFFSET) {
357
- children.push(createElement('symbiote-view', {
385
+ children.push(createElement('view', {
358
386
  key: 'spacer-leading',
359
387
  style: horizontal
360
388
  ? { width: plan.leadingExtent }
@@ -374,7 +402,11 @@ export function VirtualizedList(props) {
374
402
  index: forcedIndex,
375
403
  separators: makeSeparators(forcedIndex),
376
404
  });
377
- children.push(createElement('symbiote-view', {
405
+ children.push(createElement(
406
+ // Sticky by construction — `forcedStickyCell` exists only for an index in `stickySet` —
407
+ // and the SAME tag the in-window branch below uses, so a cell crossing between the two
408
+ // keeps its element type as well as its key and never remounts.
409
+ 'sticky-header', {
378
410
  key: `cell-${plan.forcedStickyCell.key}`,
379
411
  onLayout: makeCellMeasure(forcedIndex),
380
412
  style: inverted
@@ -385,7 +417,7 @@ export function VirtualizedList(props) {
385
417
  }, forcedCell));
386
418
  }
387
419
  if (plan.gapExtent > EMPTY_OFFSET) {
388
- children.push(createElement('symbiote-view', {
420
+ children.push(createElement('view', {
389
421
  key: 'spacer-gap',
390
422
  style: horizontal
391
423
  ? { width: plan.gapExtent }
@@ -420,7 +452,13 @@ export function VirtualizedList(props) {
420
452
  : undefined;
421
453
  // Wrap each cell in a measuring View. When inverted, each cell carries the counter-flip so
422
454
  // its content reads upright inside the flipped content container.
423
- children.push(createElement('symbiote-view', {
455
+ //
456
+ // A cell the app flagged sticky is wrapped in the `sticky-header` TAG instead — same
457
+ // position, same key, same `onLayout` (the behavior forwards it rather than replacing it),
458
+ // and the engine finds this scroll view by walking up. Nothing here computes a child index:
459
+ // the collision point comes from the owner's DOCUMENT order, which is the one form that
460
+ // survives windowing.
461
+ children.push(createElement(stickySet?.has(planCell.index) === true ? 'sticky-header' : 'view', {
424
462
  key: `cell-${planCell.key}`,
425
463
  onLayout: makeCellMeasure(planCell.index),
426
464
  style: inverted
@@ -431,7 +469,7 @@ export function VirtualizedList(props) {
431
469
  }, cell, separator));
432
470
  }
433
471
  if (plan.trailingExtent > EMPTY_OFFSET) {
434
- children.push(createElement('symbiote-view', {
472
+ children.push(createElement('view', {
435
473
  key: 'spacer-trailing',
436
474
  style: horizontal
437
475
  ? { width: plan.trailingExtent }
@@ -441,7 +479,7 @@ export function VirtualizedList(props) {
441
479
  }
442
480
  const footer = resolveElement(ListFooterComponent);
443
481
  if (footer !== undefined) {
444
- children.push(createElement('symbiote-view', { key: 'list-footer' }, footer));
482
+ children.push(createElement('view', { key: 'list-footer' }, footer));
445
483
  }
446
484
  // A horizontal list pins the content container to the full row width so the row overflows for
447
485
  // iOS to scroll. The inversion flip rides ONLY the outer ScrollView style and each cell, never
@@ -458,10 +496,13 @@ export function VirtualizedList(props) {
458
496
  ...accessibilityRest,
459
497
  style: resolvedStyle,
460
498
  contentContainerStyle: resolvedContentContainerStyle,
461
- horizontal,
462
499
  onScroll,
463
500
  onLayout: onViewportLayout,
464
501
  };
502
+ // `horizontal` is NOT in the bag: the axis is the TAG (`horizontal-scroll-view`), because Android
503
+ // scrolls the two axes with different native ViewManagers. The behavior's own fold deletes the
504
+ // prop and rewrites it from the tag, so passing it would be at best redundant and at worst a
505
+ // contradiction it has to warn about.
465
506
  if (onScrollBeginDrag !== undefined)
466
507
  scrollProps.onScrollBeginDrag = onScrollBeginDrag;
467
508
  if (onScrollEndDrag !== undefined)
@@ -479,9 +520,6 @@ export function VirtualizedList(props) {
479
520
  // A pending imperative/initial scroll rides down as contentOffset (fresh identity each push).
480
521
  if (commandedOffset !== undefined)
481
522
  scrollProps.contentOffset = commandedOffset;
482
- // Headers in the window stick; an empty list leaves the prop off entirely.
483
- if (renderedStickyIndices.length > 0)
484
- scrollProps.stickyHeaderIndices = renderedStickyIndices;
485
523
  // Forward maintainVisibleContentPosition to the native ScrollView so it anchors in-window cells.
486
524
  // minIndexForVisible is bumped by 1 when a ListHeaderComponent occupies child 0.
487
525
  if (maintainVisibleContentPosition !== undefined) {
@@ -491,16 +529,23 @@ export function VirtualizedList(props) {
491
529
  (header !== undefined ? 1 : 0),
492
530
  };
493
531
  }
494
- // Pull-to-refresh: when onRefresh is set, build a RefreshControl for the ScrollView's
495
- // refreshControl prop. refreshing is RN-required alongside onRefresh, default false when nullish.
496
- if (onRefresh !== undefined) {
497
- dlog('VirtualizedList wiring RefreshControl (onRefresh provided)');
498
- scrollProps.refreshControl = createElement(RefreshControl, {
532
+ // Pull-to-refresh: when onRefresh is set, the RefreshControl is an ordinary FIRST CHILD on both
533
+ // platforms rather than a prop. The scroll behavior CLAIMS it, so the engine keeps it beside the
534
+ // content view on iOS and inverts the tree on Android; everything after it lands in the content
535
+ // node the behavior built. refreshing is RN-required alongside onRefresh, default false.
536
+ const refreshControl = onRefresh !== undefined
537
+ ? createElement('refresh-control', {
538
+ key: 'refresh-control',
499
539
  refreshing: refreshing ?? false,
500
540
  onRefresh,
501
541
  progressViewOffset,
502
- });
542
+ })
543
+ : undefined;
544
+ if (refreshControl !== undefined) {
545
+ dlog('VirtualizedList wiring RefreshControl (onRefresh provided)');
503
546
  }
504
- // The ScrollView handle (ref) backs animated imperative scrolls via its native command.
505
- return createElement(ScrollView, { ...scrollProps, ref: scrollViewRef }, ...children);
547
+ // The scroll TAG, not a wrapper: the engine builds the content node, carries
548
+ // `contentContainerStyle` onto it and places the refresh control. `ref` hands back the engine
549
+ // node, which is what `buildScrollViewHandle` drives for the imperative scrolls.
550
+ return createElement(horizontal ? 'horizontal-scroll-view' : 'scroll-view', { ...scrollProps, ref: scrollNodeRef }, refreshControl, ...children);
506
551
  }
@@ -42,6 +42,7 @@ export interface ITextProps extends IAccessibilityProps, IAriaProps {
42
42
  numberOfLines?: number;
43
43
  ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
44
44
  selectable?: boolean;
45
+ disabled?: boolean;
45
46
  adjustsFontSizeToFit?: boolean;
46
47
  minimumFontScale?: number;
47
48
  allowFontScaling?: boolean;
@@ -50,5 +51,3 @@ export interface ITextProps extends IAccessibilityProps, IAriaProps {
50
51
  ref?: Ref<IHostInstance>;
51
52
  children?: ReactNode;
52
53
  }
53
- export declare const View = "symbiote-view";
54
- export declare const Text = "symbiote-text";