@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.
- package/README.md +12 -11
- package/build/components/activity-indicator-props.d.ts +4 -0
- package/build/components/{button.d.ts → button-props.d.ts} +0 -2
- package/build/components/button-props.js +1 -0
- package/build/components/flat-list/index.js +2 -2
- package/build/components/image/image-props.d.ts +4 -0
- package/build/components/image/image-props.js +1 -0
- package/build/components/image-background-props.d.ts +9 -0
- package/build/components/image-background-props.js +1 -0
- package/build/components/input-accessory-view-props.d.ts +10 -0
- package/build/components/input-accessory-view-props.js +1 -0
- package/build/components/keyboard-avoiding-view/index.js +4 -4
- package/build/components/modal/index.js +3 -3
- package/build/components/pressable/{index.d.ts → pressable-props.d.ts} +4 -5
- package/build/components/pressable/pressable-props.js +1 -0
- package/build/components/{refresh-control/index.d.ts → refresh-control-props.d.ts} +3 -3
- package/build/components/refresh-control-props.js +1 -0
- package/build/components/safe-area-view-props.d.ts +11 -0
- package/build/components/safe-area-view-props.js +1 -0
- package/build/components/scroll-view/{shared.d.ts → scroll-view-props.d.ts} +4 -26
- package/build/components/scroll-view/scroll-view-props.js +1 -0
- package/build/components/switch/switch-props.d.ts +4 -0
- package/build/components/switch/switch-props.js +1 -0
- package/build/components/text-input/text-input-props.d.ts +4 -0
- package/build/components/text-input/text-input-props.js +1 -0
- package/build/components/touchable/touchable-props.d.ts +17 -0
- package/build/components/touchable/touchable-props.js +1 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +6 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
- package/build/components/virtualized-list/index.js +83 -38
- package/build/components.d.ts +1 -2
- package/build/components.js +5 -19
- package/build/create-portal/index.js +1 -1
- package/build/create-tunnel/index.js +1 -1
- package/build/descriptor-to-react/index.js +1 -1
- package/build/host-config.js +22 -4
- package/build/index.d.ts +23 -28
- package/build/index.js +14 -14
- package/build/jsx-dev-runtime.d.ts +3 -0
- package/build/jsx-dev-runtime.js +10 -0
- package/build/jsx-namespace.type-check.d.ts +1 -0
- package/build/jsx-namespace.type-check.js +7 -0
- package/build/jsx-runtime.d.ts +62 -0
- package/build/jsx-runtime.js +1 -0
- package/build/modules/animated/index.d.ts +0 -1
- package/build/modules/animated/index.js +16 -22
- package/build/modules/image/index.d.ts +3 -0
- package/build/modules/image/index.js +14 -0
- package/build/register.d.ts +1 -0
- package/build/register.js +15 -0
- package/metro-css-parser.cjs +2 -1
- package/package.json +12 -8
- package/build/components/activity-indicator/index.android.d.ts +0 -3
- package/build/components/activity-indicator/index.android.js +0 -17
- package/build/components/activity-indicator/index.d.ts +0 -1
- package/build/components/activity-indicator/index.ios.d.ts +0 -3
- package/build/components/activity-indicator/index.ios.js +0 -16
- package/build/components/activity-indicator/index.js +0 -4
- package/build/components/activity-indicator/shared.d.ts +0 -6
- package/build/components/activity-indicator/shared.js +0 -22
- package/build/components/button.js +0 -42
- package/build/components/image/index.d.ts +0 -9
- package/build/components/image/index.js +0 -28
- package/build/components/image-background/index.d.ts +0 -10
- package/build/components/image-background/index.js +0 -46
- package/build/components/input-accessory-view/index.d.ts +0 -11
- package/build/components/input-accessory-view/index.js +0 -19
- package/build/components/pressable/index.js +0 -112
- package/build/components/refresh-control/index.js +0 -21
- package/build/components/safe-area-view/index.d.ts +0 -12
- package/build/components/safe-area-view/index.js +0 -18
- package/build/components/scroll-view/index.android.d.ts +0 -3
- package/build/components/scroll-view/index.android.js +0 -61
- package/build/components/scroll-view/index.d.ts +0 -1
- package/build/components/scroll-view/index.ios.d.ts +0 -3
- package/build/components/scroll-view/index.ios.js +0 -29
- package/build/components/scroll-view/index.js +0 -4
- package/build/components/scroll-view/shared.js +0 -240
- package/build/components/scroll-view/sticky-header.d.ts +0 -18
- package/build/components/scroll-view/sticky-header.js +0 -227
- package/build/components/switch/index.android.d.ts +0 -3
- package/build/components/switch/index.android.js +0 -18
- package/build/components/switch/index.d.ts +0 -1
- package/build/components/switch/index.ios.d.ts +0 -3
- package/build/components/switch/index.ios.js +0 -16
- package/build/components/switch/index.js +0 -6
- package/build/components/switch/shared.d.ts +0 -9
- package/build/components/switch/shared.js +0 -60
- package/build/components/text-input/index.d.ts +0 -50
- package/build/components/text-input/index.js +0 -180
- package/build/components/touchable/index.d.ts +0 -22
- package/build/components/touchable/index.js +0 -165
- package/build/components/touchable-native-feedback/index.d.ts +0 -18
- package/build/components/touchable-native-feedback/index.js +0 -32
- package/build/jsx.d.ts +0 -18
- package/build/state-style.d.ts +0 -1
- package/build/state-style.js +0 -13
- /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 —
|
|
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
|
-
<
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
</
|
|
52
|
-
</
|
|
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** — `
|
|
106
|
-
`
|
|
107
|
-
`
|
|
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 @@
|
|
|
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('
|
|
51
|
+
return createElement('view', { key, style: { flex: 1 } }, renderItem({ item, index, separators: info.separators }));
|
|
52
52
|
});
|
|
53
|
-
return createElement('
|
|
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 @@
|
|
|
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 {
|
|
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(
|
|
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
|
-
// `
|
|
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(
|
|
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 (
|
|
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
|
|
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 =
|
|
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
|
|
2
|
-
import {
|
|
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
|
|
2
|
-
import {
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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?:
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// VirtualizedList: real windowing over the
|
|
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
|
|
18
|
-
// scrollTo command
|
|
19
|
-
//
|
|
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
|
-
|
|
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 (
|
|
142
|
+
if (scrollNodeRef.current !== null) {
|
|
121
143
|
dlog(`VirtualizedList scrollTo offset=${clamped} animated=${animated} (horizontal=${isHorizontal})`);
|
|
122
|
-
|
|
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
|
-
|
|
314
|
+
scrollHandle.flashScrollIndicators?.();
|
|
293
315
|
},
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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('
|
|
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(
|
|
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('
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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,
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
|
505
|
-
|
|
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
|
}
|
package/build/components.d.ts
CHANGED
|
@@ -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";
|