@expo/ui 56.0.9 → 56.0.11
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/CHANGELOG.md +17 -0
- package/CLAUDE.md +1 -1
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +11 -5
- package/android/src/main/java/expo/modules/ui/HorizontalPagerView.kt +97 -16
- package/android/src/main/java/expo/modules/ui/colors/MaterialColors.kt +2 -0
- package/build/State/index.d.ts +6 -0
- package/build/State/index.d.ts.map +1 -0
- package/build/community/pager-view/PagerView.android.d.ts +7 -0
- package/build/community/pager-view/PagerView.android.d.ts.map +1 -0
- package/build/community/pager-view/PagerView.d.ts +8 -0
- package/build/community/pager-view/PagerView.d.ts.map +1 -0
- package/build/community/pager-view/PagerView.ios.d.ts +15 -0
- package/build/community/pager-view/PagerView.ios.d.ts.map +1 -0
- package/build/community/pager-view/index.d.ts +3 -0
- package/build/community/pager-view/index.d.ts.map +1 -0
- package/build/community/pager-view/types.d.ts +122 -0
- package/build/community/pager-view/types.d.ts.map +1 -0
- package/build/community/segmented-control/vendor/SegmentsSeparators.d.ts.map +1 -1
- package/build/jetpack-compose/HorizontalPager/index.d.ts +27 -0
- package/build/jetpack-compose/HorizontalPager/index.d.ts.map +1 -1
- package/build/jetpack-compose/Host/index.d.ts +1 -0
- package/build/jetpack-compose/Host/index.d.ts.map +1 -1
- package/build/jetpack-compose/LoadingIndicator/index.d.ts +1 -1
- package/build/jetpack-compose/LoadingIndicator/index.d.ts.map +1 -1
- package/build/jetpack-compose/SyncSwitch/index.d.ts +1 -1
- package/build/jetpack-compose/SyncSwitch/index.d.ts.map +1 -1
- package/build/jetpack-compose/TextField/index.d.ts +1 -1
- package/build/jetpack-compose/TextField/index.d.ts.map +1 -1
- package/build/jetpack-compose/index.d.ts +1 -2
- package/build/jetpack-compose/index.d.ts.map +1 -1
- package/build/swift-ui/Host/index.d.ts +1 -0
- package/build/swift-ui/Host/index.d.ts.map +1 -1
- package/build/swift-ui/ScrollView/index.d.ts +30 -0
- package/build/swift-ui/ScrollView/index.d.ts.map +1 -1
- package/build/swift-ui/SecureField/index.d.ts +1 -1
- package/build/swift-ui/SecureField/index.d.ts.map +1 -1
- package/build/swift-ui/SyncToggle/index.d.ts +1 -1
- package/build/swift-ui/SyncToggle/index.d.ts.map +1 -1
- package/build/swift-ui/TextField/index.d.ts +1 -1
- package/build/swift-ui/TextField/index.d.ts.map +1 -1
- package/build/swift-ui/index.d.ts +1 -2
- package/build/swift-ui/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/index.d.ts +25 -15
- package/build/swift-ui/modifiers/index.d.ts.map +1 -1
- package/build/swift-ui/modifiers/scrollObservation.d.ts +52 -0
- package/build/swift-ui/modifiers/scrollObservation.d.ts.map +1 -0
- package/build/swift-ui/modifiers/scrollPosition.d.ts +1 -1
- package/build/swift-ui/modifiers/scrollPosition.d.ts.map +1 -1
- package/build/swift-ui/modifiers/symbolEffect.d.ts +1 -1
- package/build/swift-ui/modifiers/symbolEffect.d.ts.map +1 -1
- package/build/universal/BottomSheet/index.android.d.ts.map +1 -1
- package/build/universal/BottomSheet/index.d.ts.map +1 -1
- package/build/universal/BottomSheet/index.ios.d.ts.map +1 -1
- package/build/universal/Checkbox/index.d.ts.map +1 -1
- package/build/universal/Collapsible/index.d.ts.map +1 -1
- package/build/universal/ListItem/ListItem.d.ts.map +1 -1
- package/build/universal/RNHostView/index.android.d.ts +7 -0
- package/build/universal/RNHostView/index.android.d.ts.map +1 -0
- package/build/universal/RNHostView/index.d.ts +7 -0
- package/build/universal/RNHostView/index.d.ts.map +1 -0
- package/build/universal/RNHostView/index.ios.d.ts +7 -0
- package/build/universal/RNHostView/index.ios.d.ts.map +1 -0
- package/build/universal/RNHostView/types.d.ts +23 -0
- package/build/universal/RNHostView/types.d.ts.map +1 -0
- package/build/universal/Switch/index.d.ts.map +1 -1
- package/build/universal/TextInput/index.d.ts.map +1 -1
- package/build/universal/index.d.ts +1 -0
- package/build/universal/index.d.ts.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/ExpoUIModule.swift +6 -3
- package/ios/HostView.swift +21 -18
- package/ios/Modifiers/FontModifier.swift +72 -20
- package/ios/Modifiers/ScrollObservationModifiers.swift +107 -0
- package/ios/Modifiers/ViewModifierRegistry.swift +8 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.9/expo.modules.ui-56.0.9-sources.jar → 56.0.11/expo.modules.ui-56.0.11-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.9/expo.modules.ui-56.0.9.module → 56.0.11/expo.modules.ui-56.0.11.module} +22 -22
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.module.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/{56.0.9/expo.modules.ui-56.0.9.pom → 56.0.11/expo.modules.ui-56.0.11.pom} +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.11/expo.modules.ui-56.0.11.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
- package/package.json +7 -3
- package/src/State/index.ts +10 -0
- package/src/State/useNativeState.ts +8 -0
- package/src/community/pager-view/PagerView.android.tsx +224 -0
- package/src/community/pager-view/PagerView.ios.tsx +273 -0
- package/src/community/pager-view/PagerView.tsx +14 -0
- package/src/community/pager-view/index.tsx +13 -0
- package/src/community/pager-view/types.tsx +131 -0
- package/src/community/picker/Picker.android.tsx +1 -1
- package/src/community/segmented-control/vendor/SegmentsSeparators.tsx +3 -6
- package/src/jetpack-compose/HorizontalPager/index.tsx +89 -18
- package/src/jetpack-compose/Host/index.tsx +1 -0
- package/src/jetpack-compose/LoadingIndicator/index.tsx +1 -2
- package/src/jetpack-compose/SyncSwitch/index.tsx +1 -3
- package/src/jetpack-compose/TextField/index.tsx +1 -4
- package/src/jetpack-compose/index.ts +1 -2
- package/src/swift-ui/Host/index.tsx +1 -0
- package/src/swift-ui/ScrollView/index.tsx +33 -0
- package/src/swift-ui/SecureField/index.tsx +1 -4
- package/src/swift-ui/SyncToggle/index.tsx +1 -3
- package/src/swift-ui/TextField/index.tsx +1 -4
- package/src/swift-ui/index.tsx +1 -3
- package/src/swift-ui/modifiers/index.ts +37 -14
- package/src/swift-ui/modifiers/scrollObservation.ts +80 -0
- package/src/swift-ui/modifiers/scrollPosition.ts +1 -2
- package/src/swift-ui/modifiers/symbolEffect.ts +1 -2
- package/src/swift-ui/withAnimation.ts +1 -1
- package/src/universal/BottomSheet/index.android.tsx +33 -10
- package/src/universal/BottomSheet/index.ios.tsx +12 -10
- package/src/universal/BottomSheet/index.tsx +3 -0
- package/src/universal/Checkbox/index.tsx +14 -2
- package/src/universal/Collapsible/index.tsx +17 -4
- package/src/universal/ListItem/ListItem.tsx +7 -2
- package/src/universal/RNHostView/index.android.tsx +33 -0
- package/src/universal/RNHostView/index.ios.tsx +12 -0
- package/src/universal/RNHostView/index.tsx +39 -0
- package/src/universal/RNHostView/types.ts +25 -0
- package/src/universal/Switch/index.tsx +7 -2
- package/src/universal/TextInput/index.tsx +12 -2
- package/src/universal/index.ts +1 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar +0 -0
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/ui/expo.modules.ui/56.0.9/expo.modules.ui-56.0.9.pom.sha512 +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
isValidElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ReactElement,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { StyleSheet } from 'react-native';
|
|
12
|
+
|
|
13
|
+
import { wrapNativeEvent, type PagerViewProps } from './types';
|
|
14
|
+
import { worklets } from '../../State';
|
|
15
|
+
import { HorizontalPager, type HorizontalPagerHandle } from '../../jetpack-compose/HorizontalPager';
|
|
16
|
+
import { Host } from '../../jetpack-compose/Host';
|
|
17
|
+
import { RNHostView } from '../../jetpack-compose/RNHostView';
|
|
18
|
+
import { type BuiltinShape, Shapes, clip, fillMaxSize } from '../../jetpack-compose/modifiers';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Drop-in replacement for `react-native-pager-view` on Android.
|
|
22
|
+
* Renders a Jetpack Compose `HorizontalPager`.
|
|
23
|
+
*/
|
|
24
|
+
export function PagerView(props: PagerViewProps) {
|
|
25
|
+
const {
|
|
26
|
+
ref,
|
|
27
|
+
initialPage = 0,
|
|
28
|
+
scrollEnabled = true,
|
|
29
|
+
pageMargin,
|
|
30
|
+
offscreenPageLimit,
|
|
31
|
+
layoutDirection,
|
|
32
|
+
onPageScroll,
|
|
33
|
+
onPageScrollStateChanged,
|
|
34
|
+
onPageSelected,
|
|
35
|
+
children,
|
|
36
|
+
style,
|
|
37
|
+
...passthrough
|
|
38
|
+
} = props;
|
|
39
|
+
|
|
40
|
+
const pagerRef = useRef<HorizontalPagerHandle>(null);
|
|
41
|
+
const [scrollEnabledState, setScrollEnabledState] = useState(scrollEnabled);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setScrollEnabledState(scrollEnabled);
|
|
44
|
+
}, [scrollEnabled]);
|
|
45
|
+
|
|
46
|
+
// Synthesize pager-view's `idle | dragging | settling` from Compose's raw
|
|
47
|
+
// signals: `isScrollInProgress` (drag or snap-animation in flight) plus
|
|
48
|
+
// drag interactions (start/stop/cancel).
|
|
49
|
+
const isScrollInProgressRef = useRef(false);
|
|
50
|
+
const isDraggingRef = useRef(false);
|
|
51
|
+
const lastEmittedScrollStateRef = useRef<'idle' | 'dragging' | 'settling' | null>(null);
|
|
52
|
+
const emitPageScrollStateIfChanged = (state: 'idle' | 'dragging' | 'settling') => {
|
|
53
|
+
if (state === lastEmittedScrollStateRef.current) return;
|
|
54
|
+
lastEmittedScrollStateRef.current = state;
|
|
55
|
+
onPageScrollStateChanged?.(wrapNativeEvent({ pageScrollState: state }));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
useImperativeHandle(
|
|
59
|
+
ref,
|
|
60
|
+
() => ({
|
|
61
|
+
setPage: (page: number) => {
|
|
62
|
+
pagerRef.current?.animateScrollToPage(page);
|
|
63
|
+
},
|
|
64
|
+
setPageWithoutAnimation: (page: number) => {
|
|
65
|
+
pagerRef.current?.scrollToPage(page);
|
|
66
|
+
},
|
|
67
|
+
setScrollEnabled: setScrollEnabledState,
|
|
68
|
+
}),
|
|
69
|
+
[]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const pages = Children.toArray(children)
|
|
73
|
+
.filter((child): child is ReactElement => isValidElement(child))
|
|
74
|
+
.map((child, index) => (
|
|
75
|
+
<RNHostView key={child.key ?? String(index)} modifiers={[fillMaxSize()]}>
|
|
76
|
+
{child}
|
|
77
|
+
</RNHostView>
|
|
78
|
+
));
|
|
79
|
+
|
|
80
|
+
const pageScrollHandler = useMemo(
|
|
81
|
+
() => (onPageScroll ? buildOnPageScrollHandler(onPageScroll) : undefined),
|
|
82
|
+
[onPageScroll]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// RN's `borderRadius` on the host View doesn't reliably clip Compose's draw
|
|
86
|
+
// pass — translate it into a Compose `clip` modifier instead.
|
|
87
|
+
const pagerModifiers = [fillMaxSize()];
|
|
88
|
+
const cornerShape = borderRadiusShape(style, layoutDirection === 'rtl');
|
|
89
|
+
if (cornerShape) {
|
|
90
|
+
pagerModifiers.push(clip(cornerShape));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Host style={style ?? { flex: 1 }} {...passthrough}>
|
|
95
|
+
<HorizontalPager
|
|
96
|
+
ref={pagerRef}
|
|
97
|
+
initialPage={initialPage}
|
|
98
|
+
userScrollEnabled={scrollEnabledState}
|
|
99
|
+
reverseLayout={layoutDirection === 'rtl'}
|
|
100
|
+
pageSpacing={pageMargin}
|
|
101
|
+
beyondViewportPageCount={offscreenPageLimit}
|
|
102
|
+
modifiers={pagerModifiers}
|
|
103
|
+
onSettledPageChange={(page) => {
|
|
104
|
+
onPageSelected?.(wrapNativeEvent({ position: page }));
|
|
105
|
+
}}
|
|
106
|
+
onPageScroll={pageScrollHandler}
|
|
107
|
+
onScrollInProgressChange={
|
|
108
|
+
onPageScrollStateChanged
|
|
109
|
+
? (inProgress) => {
|
|
110
|
+
isScrollInProgressRef.current = inProgress;
|
|
111
|
+
if (!inProgress) {
|
|
112
|
+
emitPageScrollStateIfChanged('idle');
|
|
113
|
+
} else if (!isDraggingRef.current) {
|
|
114
|
+
emitPageScrollStateIfChanged('settling');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
: undefined
|
|
118
|
+
}
|
|
119
|
+
onDragInteraction={
|
|
120
|
+
onPageScrollStateChanged
|
|
121
|
+
? (kind) => {
|
|
122
|
+
if (kind === 'start') {
|
|
123
|
+
isDraggingRef.current = true;
|
|
124
|
+
emitPageScrollStateIfChanged('dragging');
|
|
125
|
+
} else {
|
|
126
|
+
isDraggingRef.current = false;
|
|
127
|
+
emitPageScrollStateIfChanged(isScrollInProgressRef.current ? 'settling' : 'idle');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
: undefined
|
|
131
|
+
}>
|
|
132
|
+
{pages}
|
|
133
|
+
</HorizontalPager>
|
|
134
|
+
</Host>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Compose's `(currentPage, fraction ∈ [-0.5, 0.5))` is anchored to the snapped
|
|
139
|
+
// page; pager-view's `(position, offset ∈ [0, 1))` is anchored to the leading
|
|
140
|
+
// page. Re-anchor negative fractions onto the previous page.
|
|
141
|
+
function composePageToPageScroll(
|
|
142
|
+
currentPage: number,
|
|
143
|
+
currentPageOffsetFraction: number
|
|
144
|
+
): { position: number; offset: number } {
|
|
145
|
+
'worklet';
|
|
146
|
+
if (currentPageOffsetFraction >= 0) {
|
|
147
|
+
return { position: currentPage, offset: currentPageOffsetFraction };
|
|
148
|
+
}
|
|
149
|
+
return { position: currentPage - 1, offset: 1 + currentPageOffsetFraction };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Mirrors the worklet-ness of the user's callback so the per-frame mapping
|
|
153
|
+
// stays on the UI runtime when the user is also on it.
|
|
154
|
+
function buildOnPageScrollHandler(
|
|
155
|
+
userOnPageScroll: NonNullable<PagerViewProps['onPageScroll']>
|
|
156
|
+
): (currentPage: number, currentPageOffsetFraction: number) => void {
|
|
157
|
+
if (worklets?.isWorkletFunction?.(userOnPageScroll)) {
|
|
158
|
+
return (currentPage, currentPageOffsetFraction) => {
|
|
159
|
+
'worklet';
|
|
160
|
+
userOnPageScroll(
|
|
161
|
+
wrapNativeEvent(composePageToPageScroll(currentPage, currentPageOffsetFraction))
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return (currentPage, currentPageOffsetFraction) => {
|
|
166
|
+
userOnPageScroll(
|
|
167
|
+
wrapNativeEvent(composePageToPageScroll(currentPage, currentPageOffsetFraction))
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Translates RN border-radius style keys to a Compose `RoundedCornerShape`.
|
|
173
|
+
// Physical (`borderTopLeftRadius`) and logical (`borderTopStartRadius`) keys
|
|
174
|
+
// collapse onto Compose's start/end edges, swapped under RTL.
|
|
175
|
+
function borderRadiusShape(style: PagerViewProps['style'], rtl: boolean): BuiltinShape | undefined {
|
|
176
|
+
const flat = StyleSheet.flatten(style) as Record<string, unknown> | undefined;
|
|
177
|
+
if (!flat) return undefined;
|
|
178
|
+
// Compose `RoundedCornerShape` only takes Dp (numeric); RN's string values
|
|
179
|
+
// like `'50%'` are dropped — `__DEV__` warns once so the no-clip isn't silent.
|
|
180
|
+
const num = (key: string): number | undefined => {
|
|
181
|
+
const v = flat[key];
|
|
182
|
+
if (typeof v === 'number') return v > 0 ? v : undefined;
|
|
183
|
+
if (__DEV__ && typeof v === 'string') {
|
|
184
|
+
warnAboutStringBorderRadiusOnce(key, v);
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
};
|
|
188
|
+
const uniform = num('borderRadius');
|
|
189
|
+
const topLeft = num('borderTopLeftRadius');
|
|
190
|
+
const topRight = num('borderTopRightRadius');
|
|
191
|
+
const bottomLeft = num('borderBottomLeftRadius');
|
|
192
|
+
const bottomRight = num('borderBottomRightRadius');
|
|
193
|
+
const topStart = num('borderTopStartRadius') ?? (rtl ? topRight : topLeft);
|
|
194
|
+
const topEnd = num('borderTopEndRadius') ?? (rtl ? topLeft : topRight);
|
|
195
|
+
const bottomStart = num('borderBottomStartRadius') ?? (rtl ? bottomRight : bottomLeft);
|
|
196
|
+
const bottomEnd = num('borderBottomEndRadius') ?? (rtl ? bottomLeft : bottomRight);
|
|
197
|
+
const hasPerCorner =
|
|
198
|
+
topStart != null || topEnd != null || bottomStart != null || bottomEnd != null;
|
|
199
|
+
if (hasPerCorner) {
|
|
200
|
+
const fallback = uniform ?? 0;
|
|
201
|
+
return Shapes.RoundedCorner({
|
|
202
|
+
topStart: topStart ?? fallback,
|
|
203
|
+
topEnd: topEnd ?? fallback,
|
|
204
|
+
bottomStart: bottomStart ?? fallback,
|
|
205
|
+
bottomEnd: bottomEnd ?? fallback,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (uniform != null) {
|
|
209
|
+
return Shapes.RoundedCorner(uniform);
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let didWarnStringBorderRadius = false;
|
|
215
|
+
function warnAboutStringBorderRadiusOnce(key: string, value: string): void {
|
|
216
|
+
if (didWarnStringBorderRadius) return;
|
|
217
|
+
didWarnStringBorderRadius = true;
|
|
218
|
+
console.warn(
|
|
219
|
+
`[expo-ui PagerView] ${key}: ${JSON.stringify(value)} — string border-radius values ` +
|
|
220
|
+
`aren't supported on the Android pager (Jetpack Compose's RoundedCornerShape requires ` +
|
|
221
|
+
`numeric Dp values). The corner radius is being dropped, so the pager won't clip. ` +
|
|
222
|
+
`Use a numeric pixel value, or omit the style key.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
isValidElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactElement,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { Platform } from 'react-native';
|
|
13
|
+
|
|
14
|
+
import { wrapNativeEvent, type PagerViewProps } from './types';
|
|
15
|
+
import { useNativeState, worklets } from '../../State';
|
|
16
|
+
import { Group } from '../../swift-ui/Group';
|
|
17
|
+
import { Host } from '../../swift-ui/Host';
|
|
18
|
+
import { LazyHStack } from '../../swift-ui/LazyHStack';
|
|
19
|
+
import { RNHostView } from '../../swift-ui/RNHostView';
|
|
20
|
+
import { ScrollView, type ScrollGeometry, type ScrollPhase } from '../../swift-ui/ScrollView';
|
|
21
|
+
import {
|
|
22
|
+
containerRelativeFrame,
|
|
23
|
+
id,
|
|
24
|
+
onScrollPhaseChange,
|
|
25
|
+
scrollDisabled,
|
|
26
|
+
scrollPosition,
|
|
27
|
+
scrollTargetBehavior,
|
|
28
|
+
scrollTargetLayout,
|
|
29
|
+
useScrollGeometryChange,
|
|
30
|
+
} from '../../swift-ui/modifiers';
|
|
31
|
+
import { Animation } from '../../swift-ui/modifiers/animation';
|
|
32
|
+
import { withAnimation } from '../../swift-ui/withAnimation';
|
|
33
|
+
|
|
34
|
+
function phaseToPageState(phase: ScrollPhase): 'idle' | 'dragging' | 'settling' {
|
|
35
|
+
switch (phase) {
|
|
36
|
+
case 'idle':
|
|
37
|
+
return 'idle';
|
|
38
|
+
case 'tracking':
|
|
39
|
+
case 'interacting':
|
|
40
|
+
return 'dragging';
|
|
41
|
+
case 'animating':
|
|
42
|
+
case 'decelerating':
|
|
43
|
+
return 'settling';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Drop-in replacement for `react-native-pager-view` on iOS.
|
|
49
|
+
*
|
|
50
|
+
* Renders a SwiftUI `ScrollView` with paging behavior. Scroll position is the
|
|
51
|
+
* single source of truth: an `ObservableState` bound through the
|
|
52
|
+
* `scrollPosition` modifier drives initial placement, user-swipe writeback,
|
|
53
|
+
* and imperative `setPage` / `setPageWithoutAnimation`. The animated path
|
|
54
|
+
* routes the write through `withAnimation`; if `react-native-worklets` isn't
|
|
55
|
+
* installed, `setPage` falls back to a non-animated jump. Requires iOS 17+.
|
|
56
|
+
* Continuous progress (`onPageScroll`) and scroll state
|
|
57
|
+
* (`onPageScrollStateChanged`) events require iOS 18+.
|
|
58
|
+
*/
|
|
59
|
+
export function PagerView(props: PagerViewProps) {
|
|
60
|
+
const {
|
|
61
|
+
ref,
|
|
62
|
+
initialPage = 0,
|
|
63
|
+
scrollEnabled = true,
|
|
64
|
+
onPageScroll,
|
|
65
|
+
onPageScrollStateChanged,
|
|
66
|
+
onPageSelected,
|
|
67
|
+
children,
|
|
68
|
+
style,
|
|
69
|
+
// Drop pager-view-only props that aren't meaningful on iOS so they don't
|
|
70
|
+
// collide with `Host`'s own prop surface.
|
|
71
|
+
layoutDirection: _layoutDirection,
|
|
72
|
+
offscreenPageLimit: _offscreenPageLimit,
|
|
73
|
+
pageMargin: _pageMargin,
|
|
74
|
+
...passthrough
|
|
75
|
+
} = props;
|
|
76
|
+
|
|
77
|
+
warnIfPreIOS18ScrollCallbacksDropped(onPageScroll, onPageScrollStateChanged);
|
|
78
|
+
|
|
79
|
+
const [scrollEnabledState, setScrollEnabledState] = useState(scrollEnabled);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
setScrollEnabledState(scrollEnabled);
|
|
82
|
+
}, [scrollEnabled]);
|
|
83
|
+
|
|
84
|
+
const validChildren = useMemo(
|
|
85
|
+
() => Children.toArray(children).filter((c): c is ReactElement => isValidElement(c)),
|
|
86
|
+
[children]
|
|
87
|
+
);
|
|
88
|
+
const pageCount = validChildren.length;
|
|
89
|
+
const pageCountRef = useRef(0);
|
|
90
|
+
pageCountRef.current = pageCount;
|
|
91
|
+
|
|
92
|
+
// Clamp on first render — out-of-range initials produce an id that matches
|
|
93
|
+
// no `Group`, leaving `.scrollPosition(id:)` silently stuck at 0.
|
|
94
|
+
const [clampedInitialPage] = useState(() => {
|
|
95
|
+
if (pageCount === 0) return 0;
|
|
96
|
+
return Math.max(0, Math.min(pageCount - 1, initialPage));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const activePageState = useNativeState<string | null>(
|
|
100
|
+
clampedInitialPage > 0 ? String(clampedInitialPage) : null
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// The SwiftUI writeback fires for both user swipes and our own imperative
|
|
104
|
+
// writes; dedup against this ref so `onPageSelected` fires once per change.
|
|
105
|
+
const lastSelectedPageRef = useRef(clampedInitialPage);
|
|
106
|
+
|
|
107
|
+
// Read the latest `onPageSelected` through a ref so the shrink-clamp effect
|
|
108
|
+
// doesn't need it in deps (and won't re-run / double-fire on callback
|
|
109
|
+
// identity changes).
|
|
110
|
+
const onPageSelectedRef = useRef(onPageSelected);
|
|
111
|
+
onPageSelectedRef.current = onPageSelected;
|
|
112
|
+
|
|
113
|
+
const handleScrolledIDChange = (newId: string | null) => {
|
|
114
|
+
if (newId == null) return;
|
|
115
|
+
const page = parseInt(newId, 10);
|
|
116
|
+
if (!Number.isFinite(page)) return;
|
|
117
|
+
if (page !== lastSelectedPageRef.current) {
|
|
118
|
+
lastSelectedPageRef.current = page;
|
|
119
|
+
onPageSelectedRef.current?.(wrapNativeEvent({ position: page }));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Without clamping, an out-of-range id would silently no-op on
|
|
124
|
+
// `.scrollPosition(id:)` instead of jumping to the nearest page.
|
|
125
|
+
const clampPage = (page: number): number | null => {
|
|
126
|
+
const count = pageCountRef.current;
|
|
127
|
+
if (count === 0 || !Number.isFinite(page)) return null;
|
|
128
|
+
return Math.max(0, Math.min(count - 1, page));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Bypasses the public `value` setter on JS-thread writes — its dev warning
|
|
132
|
+
// is aimed at user code; our own imperative API is an intentional JS-thread
|
|
133
|
+
// writer. Inside a worklet we use `activePageState.value = …` directly,
|
|
134
|
+
// which routes through the SharedObject prototype installed by `index.fx`.
|
|
135
|
+
const writePageFromJS = (id: string) => {
|
|
136
|
+
(activePageState as unknown as { setValue(v: { value: string }): void }).setValue({
|
|
137
|
+
value: id,
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Re-anchor when the page count drops past the current selection. Matches
|
|
142
|
+
// Android, where Compose's `PagerState` clamps `currentPage` to the new
|
|
143
|
+
// max and `settledPage` fires the corresponding event. Without this on
|
|
144
|
+
// iOS, `.scrollPosition(id:)` silently no-ops on the missing id and the
|
|
145
|
+
// pager visually drifts without firing `onPageSelected`.
|
|
146
|
+
useLayoutEffect(() => {
|
|
147
|
+
if (pageCount === 0) return;
|
|
148
|
+
if (lastSelectedPageRef.current < pageCount) return;
|
|
149
|
+
const clamped = pageCount - 1;
|
|
150
|
+
lastSelectedPageRef.current = clamped;
|
|
151
|
+
writePageFromJS(String(clamped));
|
|
152
|
+
onPageSelectedRef.current?.(wrapNativeEvent({ position: clamped }));
|
|
153
|
+
}, [pageCount]);
|
|
154
|
+
|
|
155
|
+
useImperativeHandle(
|
|
156
|
+
ref,
|
|
157
|
+
() => ({
|
|
158
|
+
setPage: (page: number) => {
|
|
159
|
+
const clamped = clampPage(page);
|
|
160
|
+
if (clamped == null) return;
|
|
161
|
+
const nextId = String(clamped);
|
|
162
|
+
// `withAnimation` requires `react-native-worklets`; fall back to an
|
|
163
|
+
// instant jump if it isn't installed.
|
|
164
|
+
if (worklets) {
|
|
165
|
+
// `null` would disable animation; `Animation.default` opts into
|
|
166
|
+
// SwiftUI's default paging animation.
|
|
167
|
+
withAnimation(Animation.default, () => {
|
|
168
|
+
'worklet';
|
|
169
|
+
activePageState.value = nextId;
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
writePageFromJS(nextId);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
setPageWithoutAnimation: (page: number) => {
|
|
176
|
+
const clamped = clampPage(page);
|
|
177
|
+
if (clamped == null) return;
|
|
178
|
+
writePageFromJS(String(clamped));
|
|
179
|
+
},
|
|
180
|
+
setScrollEnabled: setScrollEnabledState,
|
|
181
|
+
}),
|
|
182
|
+
[activePageState]
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const handleScrollGeometry = useMemo(
|
|
186
|
+
() => (onPageScroll ? buildHandleScrollGeometry(onPageScroll) : undefined),
|
|
187
|
+
[onPageScroll]
|
|
188
|
+
);
|
|
189
|
+
const geometryModifier = useScrollGeometryChange(handleScrollGeometry);
|
|
190
|
+
|
|
191
|
+
const phaseModifier = onPageScrollStateChanged
|
|
192
|
+
? onScrollPhaseChange((phase) =>
|
|
193
|
+
onPageScrollStateChanged(wrapNativeEvent({ pageScrollState: phaseToPageState(phase) }))
|
|
194
|
+
)
|
|
195
|
+
: null;
|
|
196
|
+
|
|
197
|
+
const pages = validChildren.map((child, index) => (
|
|
198
|
+
<Group
|
|
199
|
+
key={child.key ?? String(index)}
|
|
200
|
+
modifiers={[containerRelativeFrame({ axes: 'horizontal' }), id(String(index))]}>
|
|
201
|
+
<RNHostView>{child}</RNHostView>
|
|
202
|
+
</Group>
|
|
203
|
+
));
|
|
204
|
+
|
|
205
|
+
// Toggle the flag rather than splicing the modifier in/out — SwiftUI diffs
|
|
206
|
+
// modifiers by position, so a shifting array resets the ScrollView's content.
|
|
207
|
+
const modifiers = [
|
|
208
|
+
scrollTargetBehavior('paging'),
|
|
209
|
+
scrollDisabled(!scrollEnabledState),
|
|
210
|
+
scrollPosition(activePageState, { onChange: handleScrolledIDChange }),
|
|
211
|
+
...(geometryModifier ? [geometryModifier] : []),
|
|
212
|
+
...(phaseModifier ? [phaseModifier] : []),
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<Host style={style ?? { flex: 1 }} {...passthrough}>
|
|
217
|
+
<ScrollView axes="horizontal" showsIndicators={false} modifiers={modifiers}>
|
|
218
|
+
<LazyHStack spacing={0} modifiers={[scrollTargetLayout()]}>
|
|
219
|
+
{pages}
|
|
220
|
+
</LazyHStack>
|
|
221
|
+
</ScrollView>
|
|
222
|
+
</Host>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function geometryToPageScroll(geometry: ScrollGeometry): { position: number; offset: number } {
|
|
227
|
+
'worklet';
|
|
228
|
+
// Clamp so rubber-band overscroll doesn't emit `position` outside
|
|
229
|
+
// `[0, pageCount - 1]`. `Math.round` tolerates sub-pixel content sizing.
|
|
230
|
+
const pageCount = Math.max(1, Math.round(geometry.contentWidth / geometry.containerWidth));
|
|
231
|
+
const positionFloat = Math.max(
|
|
232
|
+
0,
|
|
233
|
+
Math.min(pageCount - 1, geometry.contentOffsetX / geometry.containerWidth)
|
|
234
|
+
);
|
|
235
|
+
const position = Math.floor(positionFloat);
|
|
236
|
+
return { position, offset: positionFloat - position };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Mirrors the worklet-ness of the user's callback so the per-frame mapping
|
|
240
|
+
// stays on the UI runtime when the user is also on it.
|
|
241
|
+
function buildHandleScrollGeometry(
|
|
242
|
+
userOnPageScroll: NonNullable<PagerViewProps['onPageScroll']>
|
|
243
|
+
): (geometry: ScrollGeometry) => void {
|
|
244
|
+
if (worklets?.isWorkletFunction?.(userOnPageScroll)) {
|
|
245
|
+
return (geometry) => {
|
|
246
|
+
'worklet';
|
|
247
|
+
if (geometry.containerWidth <= 0) return;
|
|
248
|
+
userOnPageScroll(wrapNativeEvent(geometryToPageScroll(geometry)));
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return (geometry) => {
|
|
252
|
+
if (geometry.containerWidth <= 0) return;
|
|
253
|
+
userOnPageScroll(wrapNativeEvent(geometryToPageScroll(geometry)));
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let didWarnPreIOS18 = false;
|
|
258
|
+
function warnIfPreIOS18ScrollCallbacksDropped(
|
|
259
|
+
onPageScroll: PagerViewProps['onPageScroll'],
|
|
260
|
+
onPageScrollStateChanged: PagerViewProps['onPageScrollStateChanged']
|
|
261
|
+
) {
|
|
262
|
+
if (!__DEV__ || didWarnPreIOS18) return;
|
|
263
|
+
if (!onPageScroll && !onPageScrollStateChanged) return;
|
|
264
|
+
const major = parseInt(String(Platform.Version), 10);
|
|
265
|
+
if (Number.isFinite(major) && major < 18) {
|
|
266
|
+
didWarnPreIOS18 = true;
|
|
267
|
+
console.warn(
|
|
268
|
+
`[expo-ui PagerView] onPageScroll and onPageScrollStateChanged require iOS 18+ ` +
|
|
269
|
+
`and will not fire on iOS ${Platform.Version}. Guard with Platform.Version or ` +
|
|
270
|
+
`provide a fallback for older iOS targets.`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import type { PagerViewProps } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A drop-in replacement for `react-native-pager-view`. Renders a horizontally
|
|
7
|
+
* paged view backed by Jetpack Compose's `HorizontalPager` on Android and
|
|
8
|
+
* SwiftUI on iOS. Each child is treated as a separate page.
|
|
9
|
+
*/
|
|
10
|
+
export function PagerView(props: PagerViewProps): never {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`@expo/ui/community/pager-view is not implemented on ${Platform.OS}. Supported platforms: android, ios.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
PagerViewProps,
|
|
3
|
+
PagerViewRef,
|
|
4
|
+
PagerViewOnPageScrollEventData,
|
|
5
|
+
PagerViewOnPageSelectedEventData,
|
|
6
|
+
PageScrollStateChangedEventData,
|
|
7
|
+
PagerViewOnPageScrollEvent,
|
|
8
|
+
PagerViewOnPageSelectedEvent,
|
|
9
|
+
PageScrollStateChangedEvent,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
// named export is for the docs generator; default for normal consumption
|
|
13
|
+
export { PagerView, PagerView as default } from './PagerView';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ReactNode, Ref } from 'react';
|
|
2
|
+
import type { NativeSyntheticEvent, ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export type PagerViewOnPageScrollEventData = Readonly<{
|
|
5
|
+
/** Index of the leading visible page. */
|
|
6
|
+
position: number;
|
|
7
|
+
/** Fractional progress toward the next page, in `[0, 1)`. */
|
|
8
|
+
offset: number;
|
|
9
|
+
}>;
|
|
10
|
+
|
|
11
|
+
export type PagerViewOnPageSelectedEventData = Readonly<{
|
|
12
|
+
/** Index of the newly selected page. */
|
|
13
|
+
position: number;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export type PageScrollStateChangedEventData = Readonly<{
|
|
17
|
+
/**
|
|
18
|
+
* `idle` when scrolling has stopped, `dragging` while the user is actively
|
|
19
|
+
* swiping, and `settling` while the pager animates to a target page.
|
|
20
|
+
*/
|
|
21
|
+
pageScrollState: 'idle' | 'dragging' | 'settling';
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export type PagerViewOnPageScrollEvent = NativeSyntheticEvent<PagerViewOnPageScrollEventData>;
|
|
25
|
+
export type PagerViewOnPageSelectedEvent = NativeSyntheticEvent<PagerViewOnPageSelectedEventData>;
|
|
26
|
+
export type PageScrollStateChangedEvent = NativeSyntheticEvent<PageScrollStateChangedEventData>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a payload as `NativeSyntheticEvent`. We only populate `nativeEvent`
|
|
30
|
+
* since our consumers (mirroring upstream `react-native-pager-view`) read
|
|
31
|
+
* only `event.nativeEvent.X`; the unset SyntheticEvent fields would never
|
|
32
|
+
* be observed in practice.
|
|
33
|
+
*/
|
|
34
|
+
export const wrapNativeEvent = <T,>(nativeEvent: T): NativeSyntheticEvent<T> => {
|
|
35
|
+
'worklet';
|
|
36
|
+
return { nativeEvent } as NativeSyntheticEvent<T>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Props for the `PagerView` component.
|
|
41
|
+
* Compatible with `react-native-pager-view`.
|
|
42
|
+
*/
|
|
43
|
+
export type PagerViewProps = ViewProps & {
|
|
44
|
+
/**
|
|
45
|
+
* Ref handle exposing imperative `setPage`, `setPageWithoutAnimation`,
|
|
46
|
+
* and `setScrollEnabled` methods.
|
|
47
|
+
*/
|
|
48
|
+
ref?: Ref<PagerViewRef>;
|
|
49
|
+
/**
|
|
50
|
+
* Index of the page that is initially selected. Read **once** on mount;
|
|
51
|
+
* later changes are ignored. To navigate after mount, call
|
|
52
|
+
* `ref.setPage()` or `ref.setPageWithoutAnimation()`.
|
|
53
|
+
* @default 0
|
|
54
|
+
*/
|
|
55
|
+
initialPage?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the user can swipe between pages.
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
scrollEnabled?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Layout direction for paging.
|
|
63
|
+
* @default 'ltr'
|
|
64
|
+
* @platform android
|
|
65
|
+
*/
|
|
66
|
+
layoutDirection?: 'ltr' | 'rtl';
|
|
67
|
+
/**
|
|
68
|
+
* Number of pages kept off-screen on each side of the visible page.
|
|
69
|
+
* @platform android
|
|
70
|
+
*/
|
|
71
|
+
offscreenPageLimit?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Pixels of padding between pages.
|
|
74
|
+
* @platform android
|
|
75
|
+
*/
|
|
76
|
+
pageMargin?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Fires continuously while a swipe is in progress. The event's `position`
|
|
79
|
+
* is the index of the leading visible page; `offset` is the fractional
|
|
80
|
+
* progress toward the next page in the `[0, 1)` range.
|
|
81
|
+
*
|
|
82
|
+
* Mark this handler with `'worklet'` (requires `react-native-worklets`)
|
|
83
|
+
* to run it synchronously on the UI thread every frame.
|
|
84
|
+
*
|
|
85
|
+
* @platform android
|
|
86
|
+
* @platform ios 18.0+
|
|
87
|
+
*/
|
|
88
|
+
onPageScroll?: (event: PagerViewOnPageScrollEvent) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Fires when a page is fully selected. The event's `position` is the
|
|
91
|
+
* index of the new page.
|
|
92
|
+
*/
|
|
93
|
+
onPageSelected?: (event: PagerViewOnPageSelectedEvent) => void;
|
|
94
|
+
/**
|
|
95
|
+
* Fires when the scroll state changes between `idle`, `dragging`,
|
|
96
|
+
* and `settling`.
|
|
97
|
+
*
|
|
98
|
+
* @platform android
|
|
99
|
+
* @platform ios 18.0+
|
|
100
|
+
*/
|
|
101
|
+
onPageScrollStateChanged?: (event: PageScrollStateChangedEvent) => void;
|
|
102
|
+
/**
|
|
103
|
+
* Pages of the pager. Each child is treated as a separate page and
|
|
104
|
+
* stretched to fill the pager. Each child should have a stable `key`.
|
|
105
|
+
*/
|
|
106
|
+
children?: ReactNode;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Ref handle for the `PagerView` component.
|
|
111
|
+
*/
|
|
112
|
+
export type PagerViewRef = {
|
|
113
|
+
/**
|
|
114
|
+
* Animate the pager to the given page index. Out-of-range indices are
|
|
115
|
+
* silently ignored. On iOS the animation requires `react-native-worklets`;
|
|
116
|
+
* without it, `setPage` falls back to a non-animated jump.
|
|
117
|
+
*/
|
|
118
|
+
setPage: (selectedPage: number) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Jump to the given page index without an animation.
|
|
121
|
+
*/
|
|
122
|
+
setPageWithoutAnimation: (selectedPage: number) => void;
|
|
123
|
+
/**
|
|
124
|
+
* Imperatively enable or disable user scrolling.
|
|
125
|
+
*
|
|
126
|
+
* > **Note:** If the `scrollEnabled` prop is also provided, subsequent
|
|
127
|
+
* > prop changes win and reset the value set imperatively. To use the
|
|
128
|
+
* > imperative path exclusively, omit the prop.
|
|
129
|
+
*/
|
|
130
|
+
setScrollEnabled: (scrollEnabled: boolean) => void;
|
|
131
|
+
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type PickerItemValue,
|
|
8
8
|
type PickerProps,
|
|
9
9
|
} from './types';
|
|
10
|
-
import { useNativeState } from '../../State
|
|
10
|
+
import { useNativeState } from '../../State';
|
|
11
11
|
import { DropdownMenuItem } from '../../jetpack-compose/DropdownMenu/DropdownMenuItem';
|
|
12
12
|
import {
|
|
13
13
|
ExposedDropdownMenuBox,
|
|
@@ -12,7 +12,8 @@ export function SegmentsSeparators({
|
|
|
12
12
|
values: number;
|
|
13
13
|
selectedIndex?: number;
|
|
14
14
|
}) {
|
|
15
|
-
const
|
|
15
|
+
const isDark = useColorScheme() === 'dark';
|
|
16
|
+
|
|
16
17
|
const hide = (val: number) => {
|
|
17
18
|
return selectedIndex === val || selectedIndex === val + 1;
|
|
18
19
|
};
|
|
@@ -22,11 +23,7 @@ export function SegmentsSeparators({
|
|
|
22
23
|
{[...Array(values - 1).keys()].map((val) => (
|
|
23
24
|
<View
|
|
24
25
|
key={val}
|
|
25
|
-
style={[
|
|
26
|
-
styles.separator,
|
|
27
|
-
colorScheme === 'dark' && styles.darkSeparator,
|
|
28
|
-
hide(val) && styles.hide,
|
|
29
|
-
]}
|
|
26
|
+
style={[styles.separator, isDark && styles.darkSeparator, hide(val) && styles.hide]}
|
|
30
27
|
/>
|
|
31
28
|
))}
|
|
32
29
|
</View>
|