react-native-tab-view 5.0.0-alpha.0 → 5.0.0-alpha.10
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/lib/module/DefaultAdapter.android.js +4 -0
- package/lib/module/DefaultAdapter.android.js.map +1 -0
- package/lib/module/DefaultAdapter.ios.js +4 -0
- package/lib/module/DefaultAdapter.ios.js.map +1 -0
- package/lib/module/DefaultAdapter.js +5 -0
- package/lib/module/DefaultAdapter.js.map +1 -0
- package/lib/module/PagerViewAdapter.native.js +29 -13
- package/lib/module/PagerViewAdapter.native.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +3 -0
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/PlatformPressable.js +1 -1
- package/lib/module/PlatformPressable.js.map +1 -1
- package/lib/module/SceneMap.js +12 -6
- package/lib/module/SceneMap.js.map +1 -1
- package/lib/module/SceneView.js +25 -1
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/ScrollViewAdapter.js +49 -18
- package/lib/module/ScrollViewAdapter.js.map +1 -1
- package/lib/module/TabBar.js +279 -169
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +314 -90
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +94 -44
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabBarItemLabel.js +3 -2
- package/lib/module/TabBarItemLabel.js.map +1 -1
- package/lib/module/TabView.js +2 -2
- package/lib/module/TabView.js.map +1 -1
- package/lib/module/constants.js +10 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useLayoutWidths.js +46 -0
- package/lib/module/useLayoutWidths.js.map +1 -0
- package/lib/typescript/src/DefaultAdapter.android.d.ts +2 -0
- package/lib/typescript/src/DefaultAdapter.android.d.ts.map +1 -0
- package/lib/typescript/src/DefaultAdapter.d.ts +6 -0
- package/lib/typescript/src/DefaultAdapter.d.ts.map +1 -0
- package/lib/typescript/src/DefaultAdapter.ios.d.ts +2 -0
- package/lib/typescript/src/DefaultAdapter.ios.d.ts.map +1 -0
- package/lib/typescript/src/PagerViewAdapter.d.ts +1 -1
- package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PagerViewAdapter.native.d.ts +2 -2
- package/lib/typescript/src/PagerViewAdapter.native.d.ts.map +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PlatformPressable.d.ts +5 -5
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
- package/lib/typescript/src/SceneMap.d.ts +2 -2
- package/lib/typescript/src/SceneMap.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +2 -2
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/ScrollViewAdapter.d.ts +2 -3
- package/lib/typescript/src/ScrollViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/TabBar.d.ts +26 -25
- package/lib/typescript/src/TabBar.d.ts.map +1 -1
- package/lib/typescript/src/TabBarIndicator.d.ts +5 -7
- package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItem.d.ts +17 -10
- package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItemLabel.d.ts +4 -3
- package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
- package/lib/typescript/src/TabView.d.ts +111 -17
- package/lib/typescript/src/TabView.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts +8 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +13 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +31 -31
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useLayoutWidths.d.ts +2 -0
- package/lib/typescript/src/useLayoutWidths.d.ts.map +1 -0
- package/lib/typescript/src/useMeasureLayout.d.ts +1 -1
- package/lib/typescript/src/useMeasureLayout.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/DefaultAdapter.android.tsx +1 -0
- package/src/DefaultAdapter.ios.tsx +1 -0
- package/src/DefaultAdapter.tsx +13 -0
- package/src/PagerViewAdapter.native.tsx +36 -18
- package/src/PanResponderAdapter.tsx +4 -0
- package/src/PlatformPressable.tsx +11 -7
- package/src/SceneMap.tsx +17 -9
- package/src/SceneView.tsx +41 -2
- package/src/ScrollViewAdapter.tsx +88 -22
- package/src/TabBar.tsx +440 -235
- package/src/TabBarIndicator.tsx +389 -124
- package/src/TabBarItem.tsx +107 -46
- package/src/TabBarItemLabel.tsx +5 -4
- package/src/TabView.tsx +118 -20
- package/src/constants.tsx +8 -0
- package/src/index.tsx +5 -1
- package/src/types.tsx +40 -36
- package/src/useLayoutWidths.tsx +51 -0
- package/lib/module/Pager.android.js +0 -4
- package/lib/module/Pager.android.js.map +0 -1
- package/lib/module/Pager.ios.js +0 -4
- package/lib/module/Pager.ios.js.map +0 -1
- package/lib/module/Pager.js +0 -4
- package/lib/module/Pager.js.map +0 -1
- package/lib/typescript/src/Pager.android.d.ts +0 -2
- package/lib/typescript/src/Pager.android.d.ts.map +0 -1
- package/lib/typescript/src/Pager.d.ts +0 -2
- package/lib/typescript/src/Pager.d.ts.map +0 -1
- package/lib/typescript/src/Pager.ios.d.ts +0 -2
- package/lib/typescript/src/Pager.ios.d.ts.map +0 -1
- package/src/Pager.android.tsx +0 -1
- package/src/Pager.ios.tsx +0 -1
- package/src/Pager.tsx +0 -1
package/src/TabBarIndicator.tsx
CHANGED
|
@@ -8,179 +8,444 @@ import {
|
|
|
8
8
|
type ViewStyle,
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
|
|
11
|
+
import { TAB_BAR_PRIMARY_ACTIVE_COLOR } from './constants';
|
|
11
12
|
import type {
|
|
12
13
|
LocaleDirection,
|
|
13
14
|
NavigationState,
|
|
14
15
|
Route,
|
|
15
16
|
SceneRendererProps,
|
|
16
17
|
} from './types';
|
|
17
|
-
import { useAnimatedValue } from './useAnimatedValue';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
const CAP_FILL_OVERLAP = 1;
|
|
20
|
+
const EASING_SAMPLE_COUNT = 16;
|
|
21
|
+
|
|
22
|
+
const samplePoints = (easing: (t: number) => number) =>
|
|
23
|
+
Array.from({ length: EASING_SAMPLE_COUNT + 1 }, (_, i) =>
|
|
24
|
+
easing(i / EASING_SAMPLE_COUNT)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const TRAILING_EDGE_SAMPLES = samplePoints(Easing.bezier(0.3, 0, 0.8, 0.15));
|
|
28
|
+
const LEADING_EDGE_SAMPLES = samplePoints(Easing.bezier(0.05, 0.7, 0.1, 1));
|
|
20
29
|
|
|
21
30
|
export type Props<T extends Route> = SceneRendererProps & {
|
|
31
|
+
variant?: 'primary' | 'secondary' | undefined;
|
|
22
32
|
navigationState: NavigationState<T>;
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
widths: number[];
|
|
34
|
+
offsets: number[];
|
|
25
35
|
direction: LocaleDirection;
|
|
26
36
|
style?: StyleProp<ViewStyle>;
|
|
27
|
-
gap?: number;
|
|
28
|
-
children?: React.ReactNode;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const useNativeDriver = Platform.OS !== 'web';
|
|
32
|
-
|
|
33
|
-
const getTranslateX = (
|
|
34
|
-
position: Animated.AnimatedInterpolation<number>,
|
|
35
|
-
routes: Route[],
|
|
36
|
-
getTabWidth: GetTabWidth,
|
|
37
|
-
direction: LocaleDirection,
|
|
38
|
-
gap?: number,
|
|
39
|
-
width?: number | string
|
|
40
|
-
) => {
|
|
41
|
-
const inputRange = routes.map((_, i) => i);
|
|
42
|
-
|
|
43
|
-
// every index contains widths at all previous indices
|
|
44
|
-
const outputRange = routes.reduce<number[]>((acc, _, i) => {
|
|
45
|
-
if (typeof width === 'number') {
|
|
46
|
-
if (i === 0) return [getTabWidth(i) / 2 - width / 2];
|
|
47
|
-
|
|
48
|
-
let sumTabWidth = 0;
|
|
49
|
-
for (let j = 0; j < i; j++) {
|
|
50
|
-
sumTabWidth += getTabWidth(j);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return [
|
|
54
|
-
...acc,
|
|
55
|
-
sumTabWidth + getTabWidth(i) / 2 + (gap ? gap * i : 0) - width / 2,
|
|
56
|
-
];
|
|
57
|
-
} else {
|
|
58
|
-
if (i === 0) return [0];
|
|
59
|
-
return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
|
|
60
|
-
}
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
const translateX = position.interpolate({
|
|
64
|
-
inputRange,
|
|
65
|
-
outputRange,
|
|
66
|
-
extrapolate: 'clamp',
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return Animated.multiply(translateX, direction === 'rtl' ? -1 : 1);
|
|
70
37
|
};
|
|
71
38
|
|
|
72
39
|
export function TabBarIndicator<T extends Route>({
|
|
73
|
-
|
|
40
|
+
variant = 'primary',
|
|
41
|
+
widths,
|
|
42
|
+
offsets,
|
|
74
43
|
navigationState,
|
|
75
44
|
position,
|
|
76
|
-
width,
|
|
77
45
|
direction,
|
|
78
|
-
gap,
|
|
79
46
|
style,
|
|
80
|
-
children,
|
|
81
47
|
}: Props<T>) {
|
|
82
|
-
const
|
|
83
|
-
const isWidthDynamic = width === 'auto';
|
|
48
|
+
const isRTL = direction === 'rtl';
|
|
84
49
|
|
|
85
|
-
const
|
|
50
|
+
const flattenedStyle: ViewStyle =
|
|
51
|
+
StyleSheet.flatten([styles.defaults, style]) || {};
|
|
86
52
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
53
|
+
const {
|
|
54
|
+
backgroundColor,
|
|
55
|
+
borderRadius,
|
|
56
|
+
borderBottomEndRadius = borderRadius,
|
|
57
|
+
borderBottomLeftRadius = borderRadius,
|
|
58
|
+
borderBottomRightRadius = borderRadius,
|
|
59
|
+
borderBottomStartRadius = borderRadius,
|
|
60
|
+
borderTopEndRadius = borderRadius,
|
|
61
|
+
borderTopLeftRadius = borderRadius,
|
|
62
|
+
borderTopRightRadius = borderRadius,
|
|
63
|
+
borderTopStartRadius = borderRadius,
|
|
64
|
+
height,
|
|
65
|
+
start: indicatorStart,
|
|
66
|
+
end: indicatorEnd,
|
|
67
|
+
left: indicatorLeft,
|
|
68
|
+
right: indicatorRight,
|
|
69
|
+
...restStyle
|
|
70
|
+
} = flattenedStyle;
|
|
92
71
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
toValue: 1,
|
|
105
|
-
duration: 150,
|
|
106
|
-
easing: Easing.in(Easing.linear),
|
|
107
|
-
useNativeDriver,
|
|
108
|
-
}).start();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
72
|
+
delete restStyle.width;
|
|
73
|
+
delete restStyle.margin;
|
|
74
|
+
delete restStyle.marginHorizontal;
|
|
75
|
+
delete restStyle.marginStart;
|
|
76
|
+
delete restStyle.marginEnd;
|
|
77
|
+
delete restStyle.marginLeft;
|
|
78
|
+
delete restStyle.marginRight;
|
|
79
|
+
|
|
80
|
+
const containerStart =
|
|
81
|
+
indicatorStart ?? (isRTL ? indicatorRight : indicatorLeft);
|
|
82
|
+
const containerEnd = indicatorEnd ?? (isRTL ? indicatorLeft : indicatorRight);
|
|
111
83
|
|
|
112
|
-
|
|
84
|
+
if (
|
|
85
|
+
(borderBottomEndRadius != null &&
|
|
86
|
+
typeof borderBottomEndRadius !== 'number') ||
|
|
87
|
+
(borderBottomStartRadius != null &&
|
|
88
|
+
typeof borderBottomStartRadius !== 'number') ||
|
|
89
|
+
(borderBottomLeftRadius != null &&
|
|
90
|
+
typeof borderBottomLeftRadius !== 'number') ||
|
|
91
|
+
(borderBottomRightRadius != null &&
|
|
92
|
+
typeof borderBottomRightRadius !== 'number') ||
|
|
93
|
+
(borderTopEndRadius != null && typeof borderTopEndRadius !== 'number') ||
|
|
94
|
+
(borderTopStartRadius != null &&
|
|
95
|
+
typeof borderTopStartRadius !== 'number') ||
|
|
96
|
+
(borderTopLeftRadius != null && typeof borderTopLeftRadius !== 'number') ||
|
|
97
|
+
(borderTopRightRadius != null && typeof borderTopRightRadius !== 'number')
|
|
98
|
+
) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
'Only numeric border radii are supported in TabBarIndicator.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
113
103
|
|
|
114
|
-
|
|
115
|
-
|
|
104
|
+
const leftRadiusWidth = Math.max(
|
|
105
|
+
(isRTL ? borderTopEndRadius : borderTopStartRadius) ?? 0,
|
|
106
|
+
(isRTL ? borderBottomEndRadius : borderBottomStartRadius) ?? 0,
|
|
107
|
+
borderTopLeftRadius ?? 0,
|
|
108
|
+
borderBottomLeftRadius ?? 0
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const rightRadiusWidth = Math.max(
|
|
112
|
+
(isRTL ? borderTopStartRadius : borderTopEndRadius) ?? 0,
|
|
113
|
+
(isRTL ? borderBottomStartRadius : borderBottomEndRadius) ?? 0,
|
|
114
|
+
borderTopRightRadius ?? 0,
|
|
115
|
+
borderBottomRightRadius ?? 0
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const leftPieceWidth = leftRadiusWidth + CAP_FILL_OVERLAP;
|
|
119
|
+
const rightPieceWidth = rightRadiusWidth + CAP_FILL_OVERLAP;
|
|
116
120
|
|
|
117
121
|
const { routes } = navigationState;
|
|
118
122
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
123
|
+
const easedInterpolate = (values: number[], samples: number[] | null) => {
|
|
124
|
+
if (routes.length <= 1) {
|
|
125
|
+
return values[0] ?? 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// On multi-tab jumps, slide directly from source to destination and
|
|
129
|
+
// Settle one tab before the pager finishes scrolling
|
|
130
|
+
// This makes the animation feel snappier
|
|
131
|
+
// Especially on Android where the pager animation is slow
|
|
132
|
+
if (jumpRange) {
|
|
133
|
+
const fromValue = values[jumpRange.from];
|
|
134
|
+
const toValue = values[jumpRange.to];
|
|
135
|
+
|
|
136
|
+
if (fromValue == null) {
|
|
137
|
+
throw new Error(`Couldn't find a value at index ${jumpRange.from}.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (toValue == null) {
|
|
141
|
+
throw new Error(`Couldn't find a value at index ${jumpRange.to}.`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const inputRange =
|
|
145
|
+
jumpRange.from > jumpRange.to
|
|
146
|
+
? [jumpRange.to, jumpRange.to + 1, jumpRange.from]
|
|
147
|
+
: [jumpRange.from, jumpRange.to - 1, jumpRange.to];
|
|
148
|
+
|
|
149
|
+
const outputRange = inputRange.map((i) =>
|
|
150
|
+
i === jumpRange.from ? fromValue : toValue
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return position.interpolate({
|
|
154
|
+
inputRange,
|
|
155
|
+
outputRange,
|
|
156
|
+
extrapolate: 'clamp',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (samples == null) {
|
|
161
|
+
return position.interpolate({
|
|
162
|
+
inputRange: values.map((_, i) => i),
|
|
163
|
+
outputRange: values,
|
|
164
|
+
extrapolate: 'clamp',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const inputRange: number[] = [];
|
|
169
|
+
const outputRange: number[] = [];
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < values.length - 1; i++) {
|
|
172
|
+
const start = values[i];
|
|
173
|
+
const end = values[i + 1];
|
|
174
|
+
|
|
175
|
+
if (start == null) {
|
|
176
|
+
throw new Error(`Couldn't find a value at index ${i}.`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (end == null) {
|
|
180
|
+
throw new Error(`Couldn't find a value at index ${i + 1}.`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let j = 0; j < samples.length - 1; j++) {
|
|
184
|
+
const sample = samples[j];
|
|
185
|
+
|
|
186
|
+
if (sample == null) {
|
|
187
|
+
throw new Error(`Couldn't find an easing sample at index ${j}.`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
inputRange.push(i + j / EASING_SAMPLE_COUNT);
|
|
191
|
+
outputRange.push(start + (end - start) * sample);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const last = values[values.length - 1];
|
|
196
|
+
|
|
197
|
+
if (last == null) {
|
|
198
|
+
throw new Error(`Couldn't find a value at index ${values.length - 1}.`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
inputRange.push(values.length - 1);
|
|
202
|
+
outputRange.push(last);
|
|
203
|
+
|
|
204
|
+
return position.interpolate({
|
|
205
|
+
inputRange,
|
|
206
|
+
outputRange,
|
|
207
|
+
extrapolate: 'clamp',
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
let containerLayout: ViewStyle;
|
|
212
|
+
let leftFillStyle: ViewStyle;
|
|
213
|
+
let centerFillStyle: ViewStyle;
|
|
214
|
+
let rightCapStyle: ViewStyle;
|
|
215
|
+
let rightFillStyle: ViewStyle;
|
|
216
|
+
|
|
217
|
+
const rightEdges = widths.map((w, i) => {
|
|
218
|
+
const offset = offsets[i];
|
|
219
|
+
|
|
220
|
+
if (offset == null) {
|
|
221
|
+
throw new Error(`Couldn't find an offset at index ${i}.`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return offset + w;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const [lastIndex, setLastIndex] = React.useState(navigationState.index);
|
|
228
|
+
const [jumpRange, setJumpRange] = React.useState<{
|
|
229
|
+
from: number;
|
|
230
|
+
to: number;
|
|
231
|
+
} | null>(null);
|
|
232
|
+
|
|
233
|
+
if (navigationState.index !== lastIndex) {
|
|
234
|
+
setLastIndex(navigationState.index);
|
|
235
|
+
|
|
236
|
+
if (Math.abs(navigationState.index - lastIndex) > 1) {
|
|
237
|
+
setJumpRange({ from: lastIndex, to: navigationState.index });
|
|
238
|
+
}
|
|
145
239
|
}
|
|
146
240
|
|
|
147
|
-
|
|
241
|
+
React.useEffect(() => {
|
|
242
|
+
if (jumpRange == null) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const timer = setTimeout(() => {
|
|
247
|
+
setJumpRange(null);
|
|
248
|
+
}, 500);
|
|
148
249
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
250
|
+
return () => clearTimeout(timer);
|
|
251
|
+
}, [jumpRange]);
|
|
252
|
+
|
|
253
|
+
// Primary tabs use stretch animation
|
|
254
|
+
// Secondary tabs and multi-tab jumps slide linearly
|
|
255
|
+
const shouldStretch = variant === 'primary' && jumpRange == null;
|
|
256
|
+
|
|
257
|
+
const trailingSamples = shouldStretch ? TRAILING_EDGE_SAMPLES : null;
|
|
258
|
+
const leadingSamples = shouldStretch ? LEADING_EDGE_SAMPLES : null;
|
|
259
|
+
|
|
260
|
+
const offset = easedInterpolate(offsets, trailingSamples);
|
|
261
|
+
const rightEdge = easedInterpolate(rightEdges, leadingSamples);
|
|
262
|
+
|
|
263
|
+
const containerWidth = Animated.subtract(rightEdge, offset);
|
|
264
|
+
const sideFillWidth = Animated.divide(
|
|
265
|
+
Animated.subtract(containerWidth, leftPieceWidth + rightPieceWidth),
|
|
266
|
+
2
|
|
267
|
+
);
|
|
268
|
+
const sideFillScale = Animated.add(sideFillWidth, CAP_FILL_OVERLAP);
|
|
269
|
+
|
|
270
|
+
if (Platform.OS === 'web') {
|
|
271
|
+
const centerFillStart = Animated.add(sideFillWidth, leftPieceWidth);
|
|
272
|
+
|
|
273
|
+
const positioned =
|
|
274
|
+
typeof containerStart === 'number'
|
|
275
|
+
? Animated.add(offset, containerStart)
|
|
276
|
+
: offset;
|
|
277
|
+
|
|
278
|
+
// Web can't reliably scale via transforms
|
|
279
|
+
// So the fills use animated `width` instead of `scaleX`
|
|
280
|
+
// See https://github.com/react-navigation/react-navigation/pull/11440
|
|
281
|
+
containerLayout = {
|
|
282
|
+
width: containerWidth,
|
|
283
|
+
...(direction === 'rtl' ? { right: positioned } : { left: positioned }),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
leftFillStyle = {
|
|
287
|
+
position: 'absolute',
|
|
288
|
+
start: leftRadiusWidth,
|
|
289
|
+
width: sideFillScale,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
centerFillStyle = {
|
|
293
|
+
position: 'absolute',
|
|
294
|
+
start: centerFillStart,
|
|
295
|
+
width: sideFillWidth,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
rightCapStyle = {
|
|
299
|
+
position: 'absolute',
|
|
300
|
+
end: 0,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
rightFillStyle = {
|
|
304
|
+
position: 'absolute',
|
|
305
|
+
end: rightRadiusWidth,
|
|
306
|
+
width: sideFillScale,
|
|
307
|
+
};
|
|
155
308
|
} else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
309
|
+
const directionSign = direction === 'rtl' ? -1 : 1;
|
|
310
|
+
const translateX = Animated.multiply(offset, directionSign);
|
|
311
|
+
|
|
312
|
+
const centerFillTranslateX = Animated.multiply(
|
|
313
|
+
Animated.divide(sideFillWidth, 2),
|
|
314
|
+
directionSign
|
|
160
315
|
);
|
|
316
|
+
|
|
317
|
+
const rightCapTranslateX = Animated.multiply(
|
|
318
|
+
Animated.subtract(containerWidth, rightPieceWidth),
|
|
319
|
+
directionSign
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
containerLayout = {
|
|
323
|
+
start: containerStart ?? 0,
|
|
324
|
+
end: containerEnd ?? 0,
|
|
325
|
+
transform: [{ translateX }],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
leftFillStyle = {
|
|
329
|
+
position: 'absolute',
|
|
330
|
+
start: leftRadiusWidth,
|
|
331
|
+
width: 1,
|
|
332
|
+
transformOrigin: isRTL ? 'right center' : 'left center',
|
|
333
|
+
transform: [{ scaleX: sideFillScale }],
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
centerFillStyle = {
|
|
337
|
+
position: 'absolute',
|
|
338
|
+
start: leftPieceWidth,
|
|
339
|
+
width: 1,
|
|
340
|
+
transformOrigin: isRTL ? 'right center' : 'left center',
|
|
341
|
+
transform: [
|
|
342
|
+
{
|
|
343
|
+
translateX: centerFillTranslateX,
|
|
344
|
+
},
|
|
345
|
+
{ scaleX: sideFillWidth },
|
|
346
|
+
],
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
rightCapStyle = {
|
|
350
|
+
position: 'absolute',
|
|
351
|
+
start: 0,
|
|
352
|
+
transform: [{ translateX: rightCapTranslateX }],
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
rightFillStyle = {
|
|
356
|
+
position: 'absolute',
|
|
357
|
+
end: rightRadiusWidth,
|
|
358
|
+
width: 1,
|
|
359
|
+
transformOrigin: isRTL ? 'left center' : 'right center',
|
|
360
|
+
transform: [{ scaleX: sideFillScale }],
|
|
361
|
+
};
|
|
161
362
|
}
|
|
162
363
|
|
|
364
|
+
// The tab widths may be measured asynchronously
|
|
365
|
+
// So we show the indicator when we have widths till focused tab
|
|
366
|
+
const indicatorVisible = widths
|
|
367
|
+
.slice(0, navigationState.index + 1)
|
|
368
|
+
.every((w, i) => {
|
|
369
|
+
const offset = offsets[i];
|
|
370
|
+
|
|
371
|
+
return w > 0 && offset != null && offset >= 0;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* We render the indicator in multiple pieces
|
|
376
|
+
* So we can preserve border radii when the indicator is scaled with transform
|
|
377
|
+
* We use 3 pieces for the inner fill as the math isn't correct on Android
|
|
378
|
+
* So using 1 or 2 pieces result in misalignment or gaps.
|
|
379
|
+
* Using 3 pieces lets us cover most space from all directions.
|
|
380
|
+
*
|
|
381
|
+
* [left fixed cap [left scaled fill >>>]]
|
|
382
|
+
* [center scaled fill]
|
|
383
|
+
* [[<<< right scaled fill] right fixed cap]
|
|
384
|
+
*/
|
|
163
385
|
return (
|
|
164
386
|
<Animated.View
|
|
165
387
|
style={[
|
|
166
388
|
styles.indicator,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
389
|
+
{
|
|
390
|
+
height,
|
|
391
|
+
opacity: indicatorVisible ? 1 : 0,
|
|
392
|
+
},
|
|
393
|
+
containerLayout,
|
|
394
|
+
restStyle,
|
|
170
395
|
]}
|
|
171
396
|
>
|
|
172
|
-
|
|
397
|
+
<Animated.View
|
|
398
|
+
style={[
|
|
399
|
+
{
|
|
400
|
+
...(isRTL
|
|
401
|
+
? { borderTopEndRadius, borderBottomEndRadius }
|
|
402
|
+
: { borderTopStartRadius, borderBottomStartRadius }),
|
|
403
|
+
borderTopLeftRadius,
|
|
404
|
+
borderBottomLeftRadius,
|
|
405
|
+
height,
|
|
406
|
+
width: leftPieceWidth,
|
|
407
|
+
backgroundColor,
|
|
408
|
+
},
|
|
409
|
+
styles.cap,
|
|
410
|
+
]}
|
|
411
|
+
>
|
|
412
|
+
<Animated.View style={[{ height, backgroundColor }, leftFillStyle]} />
|
|
413
|
+
</Animated.View>
|
|
414
|
+
<Animated.View style={[{ height, backgroundColor }, centerFillStyle]} />
|
|
415
|
+
<Animated.View
|
|
416
|
+
style={[
|
|
417
|
+
{
|
|
418
|
+
...(isRTL
|
|
419
|
+
? { borderTopStartRadius, borderBottomStartRadius }
|
|
420
|
+
: { borderTopEndRadius, borderBottomEndRadius }),
|
|
421
|
+
borderTopRightRadius,
|
|
422
|
+
borderBottomRightRadius,
|
|
423
|
+
height,
|
|
424
|
+
width: rightPieceWidth,
|
|
425
|
+
backgroundColor,
|
|
426
|
+
},
|
|
427
|
+
rightCapStyle,
|
|
428
|
+
]}
|
|
429
|
+
>
|
|
430
|
+
<Animated.View style={[{ height, backgroundColor }, rightFillStyle]} />
|
|
431
|
+
</Animated.View>
|
|
173
432
|
</Animated.View>
|
|
174
433
|
);
|
|
175
434
|
}
|
|
176
435
|
|
|
177
436
|
const styles = StyleSheet.create({
|
|
178
437
|
indicator: {
|
|
179
|
-
|
|
438
|
+
flexDirection: 'row',
|
|
439
|
+
justifyContent: 'center',
|
|
180
440
|
position: 'absolute',
|
|
181
|
-
start: 0,
|
|
182
441
|
bottom: 0,
|
|
183
|
-
|
|
442
|
+
},
|
|
443
|
+
defaults: {
|
|
444
|
+
backgroundColor: TAB_BAR_PRIMARY_ACTIVE_COLOR,
|
|
184
445
|
height: 2,
|
|
185
446
|
},
|
|
447
|
+
cap: {
|
|
448
|
+
position: 'absolute',
|
|
449
|
+
start: 0,
|
|
450
|
+
},
|
|
186
451
|
});
|