react-native-tab-view 4.2.2 → 5.0.0-alpha.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 (70) hide show
  1. package/lib/module/Pager.android.js +1 -1
  2. package/lib/module/Pager.android.js.map +1 -1
  3. package/lib/module/Pager.ios.js +1 -1
  4. package/lib/module/Pager.ios.js.map +1 -1
  5. package/lib/module/PagerViewAdapter.js +3 -127
  6. package/lib/module/PagerViewAdapter.js.map +1 -1
  7. package/lib/module/PagerViewAdapter.native.js +130 -0
  8. package/lib/module/PagerViewAdapter.native.js.map +1 -0
  9. package/lib/module/PanResponderAdapter.js +43 -31
  10. package/lib/module/PanResponderAdapter.js.map +1 -1
  11. package/lib/module/PlatformPressable.js.map +1 -1
  12. package/lib/module/SceneView.js +22 -31
  13. package/lib/module/SceneView.js.map +1 -1
  14. package/lib/module/ScrollViewAdapter.js +210 -0
  15. package/lib/module/ScrollViewAdapter.js.map +1 -0
  16. package/lib/module/TabBar.js +98 -61
  17. package/lib/module/TabBar.js.map +1 -1
  18. package/lib/module/TabBarIndicator.js +6 -9
  19. package/lib/module/TabBarIndicator.js.map +1 -1
  20. package/lib/module/TabBarItem.js +4 -4
  21. package/lib/module/TabBarItem.js.map +1 -1
  22. package/lib/module/TabView.js +65 -84
  23. package/lib/module/TabView.js.map +1 -1
  24. package/lib/module/index.js +3 -0
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/useMeasureLayout.js +36 -0
  27. package/lib/module/useMeasureLayout.js.map +1 -0
  28. package/lib/typescript/src/PagerViewAdapter.d.ts +3 -17
  29. package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
  30. package/lib/typescript/src/PagerViewAdapter.native.d.ts +6 -0
  31. package/lib/typescript/src/PagerViewAdapter.native.d.ts.map +1 -0
  32. package/lib/typescript/src/PanResponderAdapter.d.ts +3 -17
  33. package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
  34. package/lib/typescript/src/PlatformPressable.d.ts +2 -2
  35. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
  36. package/lib/typescript/src/SceneView.d.ts +1 -1
  37. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  38. package/lib/typescript/src/ScrollViewAdapter.d.ts +12 -0
  39. package/lib/typescript/src/ScrollViewAdapter.d.ts.map +1 -0
  40. package/lib/typescript/src/TabBar.d.ts +7 -7
  41. package/lib/typescript/src/TabBar.d.ts.map +1 -1
  42. package/lib/typescript/src/TabBarIndicator.d.ts +1 -1
  43. package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
  44. package/lib/typescript/src/TabBarItem.d.ts +4 -4
  45. package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
  46. package/lib/typescript/src/TabBarItemLabel.d.ts +2 -2
  47. package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
  48. package/lib/typescript/src/TabView.d.ts +5 -5
  49. package/lib/typescript/src/TabView.d.ts.map +1 -1
  50. package/lib/typescript/src/index.d.ts +4 -1
  51. package/lib/typescript/src/index.d.ts.map +1 -1
  52. package/lib/typescript/src/types.d.ts +64 -8
  53. package/lib/typescript/src/types.d.ts.map +1 -1
  54. package/lib/typescript/src/useMeasureLayout.d.ts +5 -0
  55. package/lib/typescript/src/useMeasureLayout.d.ts.map +1 -0
  56. package/package.json +8 -8
  57. package/src/PagerViewAdapter.native.tsx +174 -0
  58. package/src/PagerViewAdapter.tsx +8 -181
  59. package/src/PanResponderAdapter.tsx +64 -77
  60. package/src/PlatformPressable.tsx +2 -1
  61. package/src/SceneView.tsx +23 -45
  62. package/src/ScrollViewAdapter.tsx +268 -0
  63. package/src/TabBar.tsx +134 -133
  64. package/src/TabBarIndicator.tsx +7 -11
  65. package/src/TabBarItem.tsx +8 -6
  66. package/src/TabBarItemLabel.tsx +2 -2
  67. package/src/TabView.tsx +79 -100
  68. package/src/index.tsx +10 -0
  69. package/src/types.tsx +75 -17
  70. package/src/useMeasureLayout.tsx +34 -0
@@ -10,35 +10,11 @@ import {
10
10
  } from 'react-native';
11
11
  import useLatestCallback from 'use-latest-callback';
12
12
 
13
- import type {
14
- EventEmitterProps,
15
- Layout,
16
- Listener,
17
- NavigationState,
18
- PagerProps,
19
- Route,
20
- } from './types';
13
+ import type { AdapterProps, Listener } from './types';
21
14
  import { useAnimatedValue } from './useAnimatedValue';
15
+ import { useMeasureLayout } from './useMeasureLayout';
22
16
 
23
- type Props<T extends Route> = PagerProps & {
24
- layout: Layout;
25
- onIndexChange: (index: number) => void;
26
- onTabSelect?: (props: { index: number }) => void;
27
- navigationState: NavigationState<T>;
28
- children: (
29
- props: EventEmitterProps & {
30
- // Animated value which represents the state of current index
31
- // It can include fractional digits as it represents the intermediate value
32
- position: Animated.AnimatedInterpolation<number>;
33
- // Function to actually render the content of the pager
34
- // The parent component takes care of rendering
35
- render: (children: React.ReactNode) => React.ReactNode;
36
- // Callback to call when switching the tab
37
- // The tab switch animation is performed even if the index in state is unchanged
38
- jumpTo: (key: string) => void;
39
- }
40
- ) => React.ReactElement;
41
- };
17
+ export type PanResponderAdapterProps = AdapterProps;
42
18
 
43
19
  const DEAD_ZONE = 12;
44
20
 
@@ -50,9 +26,8 @@ const DefaultTransitionSpec = {
50
26
  overshootClamping: true,
51
27
  };
52
28
 
53
- export function PanResponderAdapter<T extends Route>({
54
- layout,
55
- keyboardDismissMode = 'auto',
29
+ export function PanResponderAdapter({
30
+ keyboardDismissMode,
56
31
  swipeEnabled = true,
57
32
  navigationState,
58
33
  onIndexChange,
@@ -63,15 +38,17 @@ export function PanResponderAdapter<T extends Route>({
63
38
  style,
64
39
  animationEnabled = false,
65
40
  layoutDirection = 'ltr',
66
- }: Props<T>) {
41
+ }: PanResponderAdapterProps) {
67
42
  const { routes, index } = navigationState;
68
43
 
44
+ const containerRef = React.useRef<View>(null);
45
+ const [layout, onLayout] = useMeasureLayout(containerRef);
46
+
69
47
  const panX = useAnimatedValue(0);
70
48
 
71
- const listenersRef = React.useRef<Listener[]>([]);
49
+ const listeners = React.useRef<Set<Listener>>(new Set()).current;
72
50
 
73
51
  const navigationStateRef = React.useRef(navigationState);
74
- const layoutRef = React.useRef(layout);
75
52
  const onIndexChangeRef = React.useRef(onIndexChange);
76
53
  const onTabSelectRef = React.useRef(onTabSelect);
77
54
  const currentIndexRef = React.useRef(index);
@@ -82,7 +59,7 @@ export function PanResponderAdapter<T extends Route>({
82
59
 
83
60
  const jumpToIndex = useLatestCallback(
84
61
  (index: number, animate = animationEnabled) => {
85
- const offset = -index * layoutRef.current.width;
62
+ const offset = -index * layout.width;
86
63
 
87
64
  const { timing, ...transitionConfig } = DefaultTransitionSpec;
88
65
 
@@ -112,7 +89,6 @@ export function PanResponderAdapter<T extends Route>({
112
89
 
113
90
  React.useEffect(() => {
114
91
  navigationStateRef.current = navigationState;
115
- layoutRef.current = layout;
116
92
  onIndexChangeRef.current = onIndexChange;
117
93
  onTabSelectRef.current = onTabSelect;
118
94
  });
@@ -197,7 +173,12 @@ export function PanResponderAdapter<T extends Route>({
197
173
  position > index ? Math.ceil(position) : Math.floor(position);
198
174
 
199
175
  if (next !== index) {
200
- listenersRef.current.forEach((listener) => listener(next));
176
+ listeners.forEach((listener) =>
177
+ listener({
178
+ type: 'enter',
179
+ index: next,
180
+ })
181
+ );
201
182
  }
202
183
  }
203
184
 
@@ -247,15 +228,11 @@ export function PanResponderAdapter<T extends Route>({
247
228
  jumpToIndex(nextIndex, true);
248
229
  };
249
230
 
250
- const addEnterListener = useLatestCallback((listener: Listener) => {
251
- listenersRef.current.push(listener);
231
+ const subscribe = useLatestCallback((listener: Listener) => {
232
+ listeners.add(listener);
252
233
 
253
234
  return () => {
254
- const index = listenersRef.current.indexOf(listener);
255
-
256
- if (index > -1) {
257
- listenersRef.current.splice(index, 1);
258
- }
235
+ listeners.delete(listener);
259
236
  };
260
237
  });
261
238
 
@@ -295,47 +272,57 @@ export function PanResponderAdapter<T extends Route>({
295
272
 
296
273
  return children({
297
274
  position: position ?? new Animated.Value(index),
298
- addEnterListener,
275
+ subscribe,
299
276
  jumpTo,
300
277
  render: (children) => (
301
- <Animated.View
302
- style={[
303
- styles.sheet,
304
- layout.width
305
- ? {
306
- width: routes.length * layout.width,
307
- transform: [{ translateX }],
308
- }
309
- : null,
310
- style,
311
- ]}
312
- {...panResponder.panHandlers}
313
- >
314
- {React.Children.map(children, (child, i) => {
315
- const route = routes[i];
316
- const focused = i === index;
317
-
318
- return (
319
- <View
320
- key={route.key}
321
- style={
322
- layout.width
323
- ? { width: layout.width }
324
- : focused
325
- ? StyleSheet.absoluteFill
326
- : null
327
- }
328
- >
329
- {focused || layout.width ? child : null}
330
- </View>
331
- );
332
- })}
333
- </Animated.View>
278
+ <View ref={containerRef} onLayout={onLayout} style={styles.container}>
279
+ <Animated.View
280
+ style={[
281
+ styles.sheet,
282
+ layout.width
283
+ ? {
284
+ width: routes.length * layout.width,
285
+ transform: [{ translateX }],
286
+ }
287
+ : null,
288
+ style,
289
+ ]}
290
+ {...panResponder.panHandlers}
291
+ >
292
+ {children.map((child, i) => {
293
+ const route = routes[i];
294
+ const focused = i === index;
295
+
296
+ if (!layout.width && !focused) {
297
+ return null;
298
+ }
299
+
300
+ return (
301
+ <View
302
+ key={route.key}
303
+ style={
304
+ layout.width
305
+ ? { width: layout.width }
306
+ : focused
307
+ ? StyleSheet.absoluteFill
308
+ : null
309
+ }
310
+ >
311
+ {child}
312
+ </View>
313
+ );
314
+ })}
315
+ </Animated.View>
316
+ </View>
334
317
  ),
335
318
  });
336
319
  }
337
320
 
338
321
  const styles = StyleSheet.create({
322
+ container: {
323
+ flex: 1,
324
+ overflow: 'hidden',
325
+ },
339
326
  sheet: {
340
327
  flex: 1,
341
328
  flexDirection: 'row',
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import {
3
+ type ColorValue,
3
4
  type GestureResponderEvent,
4
5
  Platform,
5
6
  Pressable,
@@ -8,7 +9,7 @@ import {
8
9
 
9
10
  export type Props = Omit<PressableProps, 'onPress'> & {
10
11
  href?: string;
11
- pressColor?: string;
12
+ pressColor?: ColorValue;
12
13
  pressOpacity?: number;
13
14
  onPress?: (
14
15
  e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
package/src/SceneView.tsx CHANGED
@@ -22,43 +22,41 @@ export function SceneView<T extends Route>({
22
22
  children,
23
23
  navigationState,
24
24
  lazy,
25
- layout,
26
25
  index,
27
26
  lazyPreloadDistance,
28
- addEnterListener,
27
+ subscribe,
29
28
  style,
30
29
  }: Props<T>) {
31
- const [isLoading, setIsLoading] = React.useState(
32
- Math.abs(navigationState.index - index) > lazyPreloadDistance
33
- );
30
+ const isFocused = navigationState.index === index;
31
+ const isLoaded =
32
+ isFocused || Math.abs(navigationState.index - index) <= lazyPreloadDistance;
33
+
34
+ const [isLoading, setIsLoading] = React.useState(!isLoaded);
34
35
 
35
- if (
36
- isLoading &&
37
- Math.abs(navigationState.index - index) <= lazyPreloadDistance
38
- ) {
36
+ if (isLoading && isLoaded) {
39
37
  // Always render the route when it becomes focused
38
+ // Or close to the focused route based on preload distance
40
39
  setIsLoading(false);
41
40
  }
42
41
 
43
42
  React.useEffect(() => {
44
- const handleEnter = (value: number) => {
45
- // If we're entering the current route, we need to load it
46
- if (value === index) {
47
- setIsLoading((prevState) => {
48
- if (prevState) {
49
- return false;
50
- }
51
- return prevState;
52
- });
53
- }
54
- };
55
-
56
43
  let unsubscribe: (() => void) | undefined;
57
44
  let timer: ReturnType<typeof setTimeout> | undefined;
58
45
 
59
46
  if (lazy && isLoading) {
60
47
  // If lazy mode is enabled, listen to when we enter screens
61
- unsubscribe = addEnterListener(handleEnter);
48
+ unsubscribe = subscribe((event) => {
49
+ // If we're entering the current route, we need to load it
50
+ if (event.type === 'enter' && event.index === index) {
51
+ setIsLoading((prevState) => {
52
+ if (prevState) {
53
+ return false;
54
+ }
55
+
56
+ return prevState;
57
+ });
58
+ }
59
+ });
62
60
  } else if (isLoading) {
63
61
  // If lazy mode is not enabled, render the scene with a delay if not loaded already
64
62
  // This improves the initial startup time as the scene is no longer blocking
@@ -69,31 +67,11 @@ export function SceneView<T extends Route>({
69
67
  unsubscribe?.();
70
68
  clearTimeout(timer);
71
69
  };
72
- }, [addEnterListener, index, isLoading, lazy]);
73
-
74
- const focused = navigationState.index === index;
70
+ }, [subscribe, index, isLoading, lazy]);
75
71
 
76
72
  return (
77
- <View
78
- aria-hidden={!focused}
79
- style={[
80
- styles.route,
81
- // If we don't have the layout yet, make the focused screen fill the container
82
- // This avoids delay before we are able to render pages side by side
83
- layout.width
84
- ? { width: layout.width }
85
- : focused
86
- ? StyleSheet.absoluteFill
87
- : null,
88
- style,
89
- ]}
90
- >
91
- {
92
- // Only render the route only if it's either focused or layout is available
93
- // When layout is not available, we must not render unfocused routes
94
- // so that the focused route can fill the screen
95
- focused || layout.width ? children({ loading: isLoading }) : null
96
- }
73
+ <View aria-hidden={!isFocused} style={[styles.route, style]}>
74
+ {children({ loading: isLoading })}
97
75
  </View>
98
76
  );
99
77
  }
@@ -0,0 +1,268 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Animated,
4
+ Keyboard,
5
+ Platform,
6
+ ScrollView,
7
+ StyleSheet,
8
+ View,
9
+ type ViewProps,
10
+ } from 'react-native';
11
+ import useLatestCallback from 'use-latest-callback';
12
+
13
+ import type { AdapterProps, Listener } from './types';
14
+ import { useMeasureLayout } from './useMeasureLayout';
15
+
16
+ export type ScrollViewAdapterProps = AdapterProps &
17
+ Omit<ViewProps, 'children'> & {
18
+ decelerationRate?: 'fast' | 'normal';
19
+ keyboardShouldPersistTaps?: 'always' | 'never' | 'handled';
20
+ bounces?: boolean;
21
+ overScrollMode?: 'always' | 'never' | 'auto';
22
+ };
23
+
24
+ type ScrollEvent = Parameters<
25
+ NonNullable<React.ComponentProps<typeof Animated.ScrollView>['onScroll']>
26
+ >[0];
27
+
28
+ export function ScrollViewAdapter({
29
+ keyboardDismissMode,
30
+ swipeEnabled = true,
31
+ navigationState,
32
+ onIndexChange,
33
+ onTabSelect,
34
+ onSwipeStart,
35
+ onSwipeEnd,
36
+ children,
37
+ style,
38
+ animationEnabled = true,
39
+ layoutDirection: _, // Not supported in ScrollViewAdapter
40
+ decelerationRate = 'fast',
41
+ bounces = false,
42
+ overScrollMode = 'never',
43
+ keyboardShouldPersistTaps = 'always',
44
+ ...rest
45
+ }: ScrollViewAdapterProps) {
46
+ const { index, routes } = navigationState;
47
+
48
+ const listeners = React.useRef<Set<Listener>>(new Set()).current;
49
+
50
+ const scrollViewRef = React.useRef<ScrollView>(null);
51
+ const containerRef = React.useRef<View>(null);
52
+
53
+ const isInitialRef = React.useRef(true);
54
+
55
+ const [layout, onLayout] = useMeasureLayout(containerRef, ({ width }) => {
56
+ if (isInitialRef.current) {
57
+ const x = index * width;
58
+
59
+ setContentOffset({ x, y: 0 });
60
+
61
+ offsetX.setValue(x);
62
+ } else if (indexRef.current !== index) {
63
+ scrollToIndex(index);
64
+ }
65
+
66
+ isInitialRef.current = false;
67
+ });
68
+
69
+ const [contentOffset, setContentOffset] = React.useState(() => ({
70
+ x: index * layout.width,
71
+ y: 0,
72
+ }));
73
+
74
+ React.useEffect(() => {
75
+ // FIXME: contentOffset is not supported on Android
76
+ // So we manually scroll after state update
77
+ if (Platform.OS === 'android') {
78
+ requestAnimationFrame(() => {
79
+ scrollViewRef.current?.scrollTo({
80
+ x: contentOffset.x,
81
+ animated: false,
82
+ });
83
+ });
84
+ }
85
+ }, [animationEnabled, contentOffset.x]);
86
+
87
+ const [offsetX] = React.useState(() => new Animated.Value(contentOffset.x));
88
+
89
+ const scrollToIndex = useLatestCallback((index: number) => {
90
+ scrollViewRef.current?.scrollTo({
91
+ x: index * layout.width,
92
+ animated: animationEnabled,
93
+ });
94
+ });
95
+
96
+ const jumpTo = useLatestCallback((key: string) => {
97
+ const i = routes.findIndex((route) => route.key === key);
98
+
99
+ scrollToIndex(i);
100
+
101
+ if (keyboardDismissMode === 'auto') {
102
+ Keyboard.dismiss();
103
+ }
104
+ });
105
+
106
+ const handleSwipeStart = React.useCallback(() => {
107
+ onSwipeStart?.();
108
+ }, [onSwipeStart]);
109
+
110
+ const handleSwipeEnd = React.useCallback(() => {
111
+ onSwipeEnd?.();
112
+ }, [onSwipeEnd]);
113
+
114
+ const subscribe = useLatestCallback((listener: Listener) => {
115
+ listeners.add(listener);
116
+
117
+ return () => {
118
+ listeners.delete(listener);
119
+ };
120
+ });
121
+
122
+ const position = React.useMemo(
123
+ () => (layout.width ? Animated.divide(offsetX, layout.width) : null),
124
+ [layout.width, offsetX]
125
+ );
126
+
127
+ const indexRef = React.useRef(index);
128
+ const timerRef = React.useRef<NodeJS.Timeout | undefined>(undefined);
129
+
130
+ const onScrollEnd = (x: number) => {
131
+ const value = clamp(x / layout.width, 0, routes.length - 1);
132
+
133
+ if (value % 1 === 0) {
134
+ indexRef.current = value;
135
+
136
+ if (value !== index) {
137
+ onIndexChange(value);
138
+ }
139
+
140
+ onTabSelect?.({ index: value });
141
+ }
142
+ };
143
+
144
+ const onScroll = Animated.event(
145
+ [
146
+ {
147
+ nativeEvent: {
148
+ contentOffset: { x: offsetX },
149
+ },
150
+ },
151
+ ],
152
+ {
153
+ useNativeDriver: true,
154
+ listener: (event: ScrollEvent) => {
155
+ const { x } = event.nativeEvent.contentOffset;
156
+
157
+ const value = clamp(x / layout.width, 0, routes.length - 1);
158
+
159
+ // The offset will overlap the current and the adjacent page
160
+ // So we need to get the index of the adjacent page
161
+ const next = [Math.ceil(value), Math.floor(value)].find(
162
+ (i) => i !== index
163
+ );
164
+
165
+ if (next != null) {
166
+ listeners.forEach((listener) =>
167
+ listener({
168
+ type: 'enter',
169
+ index: next,
170
+ })
171
+ );
172
+ }
173
+
174
+ // FIXME: onMomentumScrollEnd is not supported on Web
175
+ // So we workaround by using a timer
176
+ if (Platform.OS === 'web') {
177
+ clearTimeout(timerRef.current);
178
+
179
+ timerRef.current = setTimeout(() => {
180
+ onScrollEnd(x);
181
+ }, 100);
182
+ }
183
+ },
184
+ }
185
+ );
186
+
187
+ const onMomentumScrollEnd = (event: ScrollEvent) => {
188
+ onScrollEnd(event.nativeEvent.contentOffset.x);
189
+ };
190
+
191
+ return children({
192
+ position: position ?? new Animated.Value(index),
193
+ subscribe,
194
+ jumpTo,
195
+ render: (children) => (
196
+ <View ref={containerRef} onLayout={onLayout} style={styles.container}>
197
+ <Animated.ScrollView
198
+ {...rest}
199
+ ref={scrollViewRef}
200
+ horizontal
201
+ pagingEnabled
202
+ directionalLockEnabled
203
+ decelerationRate={decelerationRate}
204
+ bounces={bounces}
205
+ overScrollMode={overScrollMode}
206
+ keyboardShouldPersistTaps={keyboardShouldPersistTaps}
207
+ keyboardDismissMode={
208
+ keyboardDismissMode === 'auto' ? 'on-drag' : keyboardDismissMode
209
+ }
210
+ scrollEnabled={swipeEnabled && Boolean(layout.width)}
211
+ scrollToOverflowEnabled={false}
212
+ scrollsToTop={false}
213
+ automaticallyAdjustContentInsets={false}
214
+ showsHorizontalScrollIndicator={false}
215
+ scrollEventThrottle={1}
216
+ onScroll={onScroll}
217
+ onScrollBeginDrag={handleSwipeStart}
218
+ onScrollEndDrag={handleSwipeEnd}
219
+ onMomentumScrollEnd={onMomentumScrollEnd}
220
+ contentOffset={contentOffset}
221
+ contentContainerStyle={{
222
+ width: layout.width ? `${routes.length * 100}%` : '100%',
223
+ }}
224
+ style={[styles.scroll, style]}
225
+ >
226
+ {children
227
+ .map((child, i) => {
228
+ const route = routes[i];
229
+ const focused = i === index;
230
+
231
+ if (!layout.width && !focused) {
232
+ return null;
233
+ }
234
+
235
+ return (
236
+ <View
237
+ key={route.key}
238
+ style={
239
+ layout.width
240
+ ? // FIXME: percentage width doesn't work on web
241
+ // So we use a fixed width instead
242
+ { width: layout.width }
243
+ : { width: '100%' }
244
+ }
245
+ >
246
+ {child}
247
+ </View>
248
+ );
249
+ })
250
+ .filter((child) => child !== null)}
251
+ </Animated.ScrollView>
252
+ </View>
253
+ ),
254
+ });
255
+ }
256
+
257
+ const clamp = (value: number, min: number, max: number) =>
258
+ Math.max(min, Math.min(max, value));
259
+
260
+ const styles = StyleSheet.create({
261
+ container: {
262
+ flex: 1,
263
+ overflow: 'hidden',
264
+ },
265
+ scroll: {
266
+ flex: 1,
267
+ },
268
+ });