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
package/src/TabView.tsx CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import {
3
3
  I18nManager,
4
- type LayoutChangeEvent,
5
4
  Platform,
6
5
  type StyleProp,
7
6
  StyleSheet,
@@ -13,28 +12,30 @@ import { Pager } from './Pager';
13
12
  import { SceneView } from './SceneView';
14
13
  import { TabBar } from './TabBar';
15
14
  import type {
16
- Layout,
15
+ AdapterCommonProps,
16
+ AdapterProps,
17
+ EventEmitterProps,
17
18
  LocaleDirection,
18
19
  NavigationState,
19
- PagerProps,
20
20
  Route,
21
21
  SceneRendererProps,
22
22
  TabDescriptor,
23
23
  } from './types';
24
24
 
25
- export type Props<T extends Route> = Omit<PagerProps, 'layoutDirection'> & {
25
+ export type Props<T extends Route> = AdapterCommonProps & {
26
26
  onIndexChange: (index: number) => void;
27
27
  onTabSelect?: (props: { index: number }) => void;
28
28
  navigationState: NavigationState<T>;
29
29
  renderLazyPlaceholder?: (props: { route: T }) => React.ReactNode;
30
30
  renderTabBar?: (
31
- props: SceneRendererProps & {
32
- navigationState: NavigationState<T>;
33
- options: Record<string, TabDescriptor<T>> | undefined;
34
- }
31
+ props: SceneRendererProps &
32
+ EventEmitterProps & {
33
+ navigationState: NavigationState<T>;
34
+ options: Record<string, TabDescriptor<T>> | undefined;
35
+ }
35
36
  ) => React.ReactNode;
37
+ renderAdapter?: (props: AdapterProps) => React.ReactElement;
36
38
  tabBarPosition?: 'top' | 'bottom';
37
- initialLayout?: Partial<Layout>;
38
39
  lazy?: ((props: { route: T }) => boolean) | boolean;
39
40
  lazyPreloadDistance?: number;
40
41
  direction?: LocaleDirection;
@@ -52,7 +53,6 @@ export function TabView<T extends Route>({
52
53
  onTabSelect,
53
54
  navigationState,
54
55
  renderScene,
55
- initialLayout,
56
56
  keyboardDismissMode = 'auto',
57
57
  lazy = false,
58
58
  lazyPreloadDistance = 0,
@@ -61,13 +61,14 @@ export function TabView<T extends Route>({
61
61
  renderLazyPlaceholder = renderLazyPlaceholderDefault,
62
62
  // eslint-disable-next-line @eslint-react/no-unstable-default-props
63
63
  renderTabBar = (props) => <TabBar {...props} />,
64
+ // eslint-disable-next-line @eslint-react/no-unstable-default-props
65
+ renderAdapter = (props) => <Pager {...props} />,
64
66
  pagerStyle,
65
67
  style,
66
68
  direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
67
69
  swipeEnabled = true,
68
70
  tabBarPosition = 'top',
69
71
  animationEnabled = true,
70
- overScrollMode,
71
72
  options: sceneOptions,
72
73
  commonOptions,
73
74
  }: Props<T>) {
@@ -82,30 +83,12 @@ export function TabView<T extends Route>({
82
83
  );
83
84
  }
84
85
 
85
- const [layout, setLayout] = React.useState({
86
- width: 0,
87
- height: 0,
88
- ...initialLayout,
89
- });
90
-
91
86
  const jumpToIndex = (index: number) => {
92
87
  if (index !== navigationState.index) {
93
88
  onIndexChange(index);
94
89
  }
95
90
  };
96
91
 
97
- const handleLayout = (e: LayoutChangeEvent) => {
98
- const { height, width } = e.nativeEvent.layout;
99
-
100
- setLayout((prevLayout) => {
101
- if (prevLayout.width === width && prevLayout.height === height) {
102
- return prevLayout;
103
- }
104
-
105
- return { height, width };
106
- });
107
- };
108
-
109
92
  const options = Object.fromEntries(
110
93
  navigationState.routes.map((route) => [
111
94
  route.key,
@@ -116,82 +99,78 @@ export function TabView<T extends Route>({
116
99
  ])
117
100
  );
118
101
 
119
- return (
120
- <View onLayout={handleLayout} style={[styles.pager, style]}>
121
- <Pager
122
- layout={layout}
123
- navigationState={navigationState}
124
- keyboardDismissMode={keyboardDismissMode}
125
- swipeEnabled={swipeEnabled}
126
- onSwipeStart={onSwipeStart}
127
- onSwipeEnd={onSwipeEnd}
128
- onIndexChange={jumpToIndex}
129
- onTabSelect={onTabSelect}
130
- animationEnabled={animationEnabled}
131
- overScrollMode={overScrollMode}
132
- style={pagerStyle}
133
- layoutDirection={direction}
134
- >
135
- {({ position, render, addEnterListener, jumpTo }) => {
136
- // All the props here must not change between re-renders
137
- // This is crucial to optimizing the routes with PureComponent
138
- const sceneRendererProps = {
139
- position,
140
- layout,
141
- jumpTo,
142
- };
102
+ const element = renderAdapter({
103
+ navigationState,
104
+ keyboardDismissMode,
105
+ swipeEnabled,
106
+ onSwipeStart,
107
+ onSwipeEnd,
108
+ onIndexChange: jumpToIndex,
109
+ onTabSelect,
110
+ animationEnabled,
111
+ layoutDirection: direction,
112
+ style: pagerStyle,
113
+ children: ({ position, render, subscribe, jumpTo }) => {
114
+ // All the props here must not change between re-renders
115
+ // This is crucial to optimizing the routes with PureComponent
116
+ const sceneRendererProps = {
117
+ position,
118
+ jumpTo,
119
+ };
143
120
 
144
- return (
145
- <React.Fragment>
146
- {tabBarPosition === 'top' &&
147
- renderTabBar({
148
- ...sceneRendererProps,
149
- options,
150
- navigationState,
151
- })}
152
- {render(
153
- navigationState.routes.map((route, i) => {
154
- const { sceneStyle } = options?.[route.key] ?? {};
121
+ return (
122
+ <React.Fragment>
123
+ {tabBarPosition === 'top' &&
124
+ renderTabBar({
125
+ ...sceneRendererProps,
126
+ subscribe,
127
+ options,
128
+ navigationState,
129
+ })}
130
+ {render(
131
+ navigationState.routes.map((route, i) => {
132
+ const { sceneStyle } = options?.[route.key] ?? {};
155
133
 
156
- return (
157
- <SceneView
158
- key={route.key}
159
- {...sceneRendererProps}
160
- addEnterListener={addEnterListener}
161
- index={i}
162
- lazy={typeof lazy === 'function' ? lazy({ route }) : lazy}
163
- lazyPreloadDistance={lazyPreloadDistance}
164
- navigationState={navigationState}
165
- style={sceneStyle}
166
- >
167
- {({ loading }) =>
168
- loading
169
- ? renderLazyPlaceholder({ route })
170
- : renderScene({
171
- ...sceneRendererProps,
172
- route,
173
- })
174
- }
175
- </SceneView>
176
- );
177
- })
178
- )}
179
- {tabBarPosition === 'bottom' &&
180
- renderTabBar({
181
- ...sceneRendererProps,
182
- options,
183
- navigationState,
184
- })}
185
- </React.Fragment>
186
- );
187
- }}
188
- </Pager>
189
- </View>
190
- );
134
+ return (
135
+ <SceneView
136
+ key={route.key}
137
+ {...sceneRendererProps}
138
+ subscribe={subscribe}
139
+ index={i}
140
+ lazy={typeof lazy === 'function' ? lazy({ route }) : lazy}
141
+ lazyPreloadDistance={lazyPreloadDistance}
142
+ navigationState={navigationState}
143
+ style={sceneStyle}
144
+ >
145
+ {({ loading }) =>
146
+ loading
147
+ ? renderLazyPlaceholder({ route })
148
+ : renderScene({
149
+ ...sceneRendererProps,
150
+ route,
151
+ })
152
+ }
153
+ </SceneView>
154
+ );
155
+ })
156
+ )}
157
+ {tabBarPosition === 'bottom' &&
158
+ renderTabBar({
159
+ ...sceneRendererProps,
160
+ subscribe,
161
+ options,
162
+ navigationState,
163
+ })}
164
+ </React.Fragment>
165
+ );
166
+ },
167
+ });
168
+
169
+ return <View style={[styles.container, style]}>{element}</View>;
191
170
  }
192
171
 
193
172
  const styles = StyleSheet.create({
194
- pager: {
173
+ container: {
195
174
  flex: 1,
196
175
  overflow: 'hidden',
197
176
  },
package/src/index.tsx CHANGED
@@ -1,4 +1,13 @@
1
+ export {
2
+ PagerViewAdapter,
3
+ type PagerViewAdapterProps,
4
+ } from './PagerViewAdapter';
5
+ export { PanResponderAdapter } from './PanResponderAdapter';
1
6
  export { SceneMap } from './SceneMap';
7
+ export {
8
+ ScrollViewAdapter,
9
+ type ScrollViewAdapterProps,
10
+ } from './ScrollViewAdapter';
2
11
  export type { Props as TabBarProps } from './TabBar';
3
12
  export { TabBar } from './TabBar';
4
13
  export type { Props as TabBarIndicatorProps } from './TabBarIndicator';
@@ -8,6 +17,7 @@ export { TabBarItem } from './TabBarItem';
8
17
  export type { Props as TabViewProps } from './TabView';
9
18
  export { TabView } from './TabView';
10
19
  export type {
20
+ AdapterProps,
11
21
  NavigationState,
12
22
  Route,
13
23
  SceneRendererProps,
package/src/types.tsx CHANGED
@@ -1,5 +1,11 @@
1
- import type { Animated, StyleProp, TextStyle, ViewStyle } from 'react-native';
2
- import type { PagerViewProps } from 'react-native-pager-view';
1
+ import * as React from 'react';
2
+ import type {
3
+ Animated,
4
+ ColorValue,
5
+ StyleProp,
6
+ TextStyle,
7
+ ViewStyle,
8
+ } from 'react-native';
3
9
 
4
10
  export type TabDescriptor<T extends Route> = {
5
11
  accessibilityLabel?: string;
@@ -12,7 +18,7 @@ export type TabDescriptor<T extends Route> = {
12
18
  route: T;
13
19
  labelText?: string;
14
20
  focused: boolean;
15
- color: string;
21
+ color: ColorValue;
16
22
  allowFontScaling?: boolean;
17
23
  style?: StyleProp<TextStyle>;
18
24
  }) => React.ReactNode;
@@ -20,7 +26,7 @@ export type TabDescriptor<T extends Route> = {
20
26
  icon?: (props: {
21
27
  route: T;
22
28
  focused: boolean;
23
- color: string;
29
+ color: ColorValue;
24
30
  size: number;
25
31
  }) => React.ReactNode;
26
32
  badge?: (props: { route: T }) => React.ReactElement;
@@ -57,31 +63,83 @@ export type Layout = {
57
63
  height: number;
58
64
  };
59
65
 
60
- export type Listener = (value: number) => void;
66
+ export type Listener = (event: { type: 'enter'; index: number }) => void;
61
67
 
62
68
  export type SceneRendererProps = {
63
- layout: Layout;
64
69
  position: Animated.AnimatedInterpolation<number>;
65
70
  jumpTo: (key: string) => void;
66
71
  };
67
72
 
68
73
  export type EventEmitterProps = {
69
- addEnterListener: (listener: Listener) => () => void;
74
+ subscribe: (listener: Listener) => () => void;
70
75
  };
71
76
 
72
- export type PagerProps = Omit<
73
- PagerViewProps,
74
- | 'initialPage'
75
- | 'scrollEnabled'
76
- | 'onPageScroll'
77
- | 'onPageSelected'
78
- | 'onPageScrollStateChanged'
79
- | 'keyboardDismissMode'
80
- | 'children'
81
- > & {
77
+ export type AdapterCommonProps = {
78
+ /**
79
+ * Determines whether the keyboard gets dismissed in response to a drag.
80
+ * - 'auto' (default) - the keyboard is dismissed on drag and tab changes
81
+ * - 'on-drag' - the keyboard is dismissed when a drag begins
82
+ * - 'none' - drags and tab changes do not dismiss the keyboard
83
+ */
82
84
  keyboardDismissMode?: 'none' | 'on-drag' | 'auto';
85
+ /**
86
+ * Whether swiping between tabs is enabled.
87
+ */
83
88
  swipeEnabled?: boolean;
89
+ /**
90
+ * Whether the tab switch animation is enabled.
91
+ * If set to false, the tab switch will happen immediately without animation.
92
+ */
84
93
  animationEnabled?: boolean;
94
+ /**
95
+ * Callback that is called when the swipe gesture starts.
96
+ */
85
97
  onSwipeStart?: () => void;
98
+ /**
99
+ * Callback that is called when the swipe gesture ends.
100
+ */
86
101
  onSwipeEnd?: () => void;
102
+ /**
103
+ * Callback that is called when a tab is selected.
104
+ * This is called regardless of whether the index has changed or not.
105
+ */
106
+ onTabSelect?: (props: { index: number }) => void;
107
+ /**
108
+ * Style for the pager adapter.
109
+ */
110
+ style?: StyleProp<ViewStyle>;
87
111
  };
112
+
113
+ export type AdapterRendererProps = {
114
+ /**
115
+ * Callback to call when the index changes.
116
+ */
117
+ onIndexChange: (index: number) => void;
118
+ /**
119
+ * The current navigation state of the tab view.
120
+ */
121
+ navigationState: NavigationState<Route>;
122
+ /**
123
+ * The writing direction of the layout.
124
+ * This can be 'ltr' or 'rtl' based on tab view's `direction` prop.
125
+ */
126
+ layoutDirection?: LocaleDirection;
127
+ /**
128
+ * Render callback that should render the pages of the tab view.
129
+ */
130
+ children: (
131
+ props: EventEmitterProps & {
132
+ // Animated value which represents the state of current index
133
+ // It can include fractional digits as it represents the intermediate value
134
+ position: Animated.AnimatedInterpolation<number>;
135
+ // Function to actually render the content of the pager
136
+ // The parent component takes care of rendering
137
+ render: (children: React.ReactElement[]) => React.ReactNode;
138
+ // Callback to call when switching the tab
139
+ // The tab switch animation is performed even if the index in state is unchanged
140
+ jumpTo: (key: string) => void;
141
+ }
142
+ ) => React.ReactElement;
143
+ };
144
+
145
+ export type AdapterProps = AdapterRendererProps & AdapterCommonProps;
@@ -0,0 +1,34 @@
1
+ import * as React from 'react';
2
+ import { type LayoutChangeEvent, type View } from 'react-native';
3
+ import useLatestCallback from 'use-latest-callback';
4
+
5
+ import type { Layout } from './types';
6
+
7
+ export function useMeasureLayout(
8
+ ref: React.RefObject<View | null>,
9
+ onMeasure?: (layout: Layout) => void
10
+ ) {
11
+ const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
12
+
13
+ const onMeasureLatest = useLatestCallback(({ width, height }: Layout) => {
14
+ setLayout((layout) =>
15
+ layout.width === width && layout.height === height
16
+ ? layout
17
+ : { width, height }
18
+ );
19
+
20
+ onMeasure?.({ width, height });
21
+ });
22
+
23
+ React.useLayoutEffect(() => {
24
+ ref.current?.measure((_x, _y, width, height) => {
25
+ onMeasureLatest({ width, height });
26
+ });
27
+ }, [onMeasureLatest, ref]);
28
+
29
+ const onLayout = useLatestCallback((event: LayoutChangeEvent) => {
30
+ onMeasureLatest(event.nativeEvent.layout);
31
+ });
32
+
33
+ return [layout, onLayout] as const;
34
+ }