@hero-design/rn 8.131.0 → 8.131.2
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 +12 -0
- package/es/index.js +40 -87
- package/lib/index.js +39 -86
- package/package.json +1 -1
- package/src/components/Icon/index.tsx +0 -2
- package/src/components/Tabs/ScrollableTabsHeader/hooks/useIndicatorAnimation.ts +68 -62
- package/src/components/Tabs/StyledScrollableTabs.tsx +4 -4
- package/src/theme/components/filterTrigger.ts +1 -1
- package/types/components/Icon/index.d.ts +0 -2
- package/types/components/Tabs/ScrollableTabsHeader/hooks/useIndicatorAnimation.d.ts +0 -38
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @hero-design/rn
|
|
2
2
|
|
|
3
|
+
## 8.131.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5161](https://github.com/Thinkei/hero-design/pull/5161) [`f9dd6c0a09ca33eac8455c8d03bbbf420f9b6d27`](https://github.com/Thinkei/hero-design/commit/f9dd6c0a09ca33eac8455c8d03bbbf420f9b6d27) Thanks [@ttkien](https://github.com/ttkien)! - [Tabs] Fix white space in Tab.Scroll header
|
|
8
|
+
|
|
9
|
+
## 8.131.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#5125](https://github.com/Thinkei/hero-design/pull/5125) [`4ffd7dc018090ad4f36fb926266f0849a4b52984`](https://github.com/Thinkei/hero-design/commit/4ffd7dc018090ad4f36fb926266f0849a4b52984) Thanks [@tqdungit](https://github.com/tqdungit)! - [FilterTrigger] Use `secondaryOutline` border color for `outlined` variant in inactive state (was incorrectly `primary`)
|
|
14
|
+
|
|
3
15
|
## 8.131.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/es/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as reactNative from 'react-native';
|
|
2
|
-
import { StyleSheet as StyleSheet$1, Platform, Dimensions, Keyboard, Animated, View, UIManager, LayoutAnimation, TouchableOpacity, Text as Text$1, Easing, useWindowDimensions, TouchableWithoutFeedback, Modal as Modal$1, Image as Image$1, Pressable, KeyboardAvoidingView, TouchableHighlight, ScrollView, FlatList, TextInput as TextInput$1, PanResponder, BackHandler, InteractionManager, SectionList, RefreshControl as RefreshControl$1 } from 'react-native';
|
|
2
|
+
import { StyleSheet as StyleSheet$1, Platform, Dimensions, Keyboard, Animated, View, UIManager, LayoutAnimation, TouchableOpacity, Text as Text$1, Easing, useWindowDimensions, TouchableWithoutFeedback, Modal as Modal$1, Image as Image$1, Pressable, KeyboardAvoidingView, TouchableHighlight, ScrollView, FlatList, TextInput as TextInput$1, PanResponder, BackHandler, InteractionManager, SectionList, PixelRatio, RefreshControl as RefreshControl$1 } from 'react-native';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import React__default, { useState, useEffect, useMemo, useCallback, useRef, useLayoutEffect, createContext, forwardRef, useContext, memo, useReducer, isValidElement, useImperativeHandle } from 'react';
|
|
5
5
|
import MaskedView from '@react-native-masked-view/masked-view';
|
|
@@ -7816,7 +7816,7 @@ var getFilterTriggerTheme = function getFilterTriggerTheme(theme) {
|
|
|
7816
7816
|
},
|
|
7817
7817
|
inactive: {
|
|
7818
7818
|
filled: theme.colors.neutralGlobalSurface,
|
|
7819
|
-
outlined: theme.colors.
|
|
7819
|
+
outlined: theme.colors.secondaryOutline,
|
|
7820
7820
|
ghost: 'transparent'
|
|
7821
7821
|
}
|
|
7822
7822
|
}
|
|
@@ -28157,7 +28157,7 @@ var HeaderTabPillBody = index$c(Animated.View)(function (_ref4) {
|
|
|
28157
28157
|
top: 0,
|
|
28158
28158
|
bottom: 0,
|
|
28159
28159
|
left: 0,
|
|
28160
|
-
width: 1,
|
|
28160
|
+
width: 1 / PixelRatio.get(),
|
|
28161
28161
|
backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground
|
|
28162
28162
|
};
|
|
28163
28163
|
});
|
|
@@ -28178,7 +28178,7 @@ var HeaderTabItemActiveBorder = index$c(Animated.View)(function (_ref6) {
|
|
|
28178
28178
|
return {
|
|
28179
28179
|
position: 'absolute',
|
|
28180
28180
|
bottom: 0,
|
|
28181
|
-
width: 1,
|
|
28181
|
+
width: 1 / PixelRatio.get(),
|
|
28182
28182
|
height: theme.__hd__.tabs.borderWidths.highlightedActiveBorder,
|
|
28183
28183
|
backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBorder
|
|
28184
28184
|
};
|
|
@@ -28196,7 +28196,7 @@ var HeaderTabItemWrapper = index$c(View)(function (_ref7) {
|
|
|
28196
28196
|
var HeaderTabItemIndicator = index$c(Animated.View)(function (_ref8) {
|
|
28197
28197
|
var theme = _ref8.theme;
|
|
28198
28198
|
return {
|
|
28199
|
-
width: 1,
|
|
28199
|
+
width: 1 / PixelRatio.get(),
|
|
28200
28200
|
height: theme.__hd__.tabs.sizes.indicator,
|
|
28201
28201
|
position: 'absolute',
|
|
28202
28202
|
bottom: theme.__hd__.tabs.space.tabIndicatorBottom,
|
|
@@ -28277,44 +28277,17 @@ var TabWithBadge = function TabWithBadge(_ref) {
|
|
|
28277
28277
|
return /*#__PURE__*/React__default.createElement(View, null, tabItem);
|
|
28278
28278
|
};
|
|
28279
28279
|
|
|
28280
|
-
/**
|
|
28281
|
-
|
|
28282
|
-
|
|
28283
|
-
|
|
28284
|
-
|
|
28285
|
-
|
|
28286
|
-
|
|
28287
|
-
|
|
28288
|
-
|
|
28289
|
-
|
|
28290
|
-
|
|
28291
|
-
* Layer 2 — pill background (pillLeftStyle / pillBodyStyle / pillRightStyle)
|
|
28292
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
28293
|
-
* The pill is split into three absolutely-positioned children so that
|
|
28294
|
-
* border-radius is never distorted by scale:
|
|
28295
|
-
*
|
|
28296
|
-
* ┌──────────────────────────────────────────────────────┐
|
|
28297
|
-
* │ [cap-left 8px] [body width-1 + scaleX] [cap-right 8px] │
|
|
28298
|
-
* └──────────────────────────────────────────────────────┘
|
|
28299
|
-
*
|
|
28300
|
-
* cap-left — fixed 8px wide, borderTopLeftRadius:8, translateX = pillX
|
|
28301
|
-
* body — width:1 + scaleX trick (scaleX = tabWidth - 16),
|
|
28302
|
-
* transformOrigin 'left center',
|
|
28303
|
-
* translateX = pillX + 8 (via Animated.add)
|
|
28304
|
-
* cap-right — fixed 8px wide, borderTopRightRadius:8,
|
|
28305
|
-
* translateX = pillX + tabWidth - 8 (via Animated.add)
|
|
28306
|
-
*
|
|
28307
|
-
* All four animated values use the native driver (translateX and scaleX are
|
|
28308
|
-
* transform properties). `width` is never animated, so no JS driver needed.
|
|
28309
|
-
*
|
|
28310
|
-
* Driver summary:
|
|
28311
|
-
* indicatorX native translateX — slides the bottom border
|
|
28312
|
-
* indicatorScaleX native scaleX — stretches the bottom border
|
|
28313
|
-
* pillX native translateX — slides all three pill pieces
|
|
28314
|
-
* pillBodyScaleX native scaleX — stretches the pill body
|
|
28315
|
-
* pillRightOffsetX native translateX — positions the right cap
|
|
28316
|
-
* (Animated.add: pillX + tabWidth - 8)
|
|
28317
|
-
*/
|
|
28280
|
+
/** Centralised calculation for all animated values given a tab layout. */
|
|
28281
|
+
var computeValues = function computeValues(layout, pillCapWidth) {
|
|
28282
|
+
var pr = PixelRatio.get();
|
|
28283
|
+
return {
|
|
28284
|
+
indicatorX: layout.x,
|
|
28285
|
+
indicatorScaleX: layout.width * pr,
|
|
28286
|
+
pillX: layout.x,
|
|
28287
|
+
pillBodyScaleX: Math.max(layout.width - pillCapWidth * 2, 0) * pr,
|
|
28288
|
+
pillRightOffset: Math.max(layout.width - pillCapWidth, 0)
|
|
28289
|
+
};
|
|
28290
|
+
};
|
|
28318
28291
|
var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
28319
28292
|
var selectedIndex = _ref.selectedIndex,
|
|
28320
28293
|
tabsLength = _ref.tabsLength,
|
|
@@ -28323,9 +28296,6 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28323
28296
|
var indicatorX = React__default.useRef(new Animated.Value(0)).current;
|
|
28324
28297
|
var indicatorScaleX = React__default.useRef(new Animated.Value(1)).current;
|
|
28325
28298
|
// Layer 2 — native driver (pill background, three-piece split).
|
|
28326
|
-
// pillX: left edge of the pill (shared by all three pieces as base).
|
|
28327
|
-
// pillBodyScaleX: scaleX for the body piece (tabWidth - 2 * CAP_WIDTH).
|
|
28328
|
-
// pillRightOffset: additional x offset for the right cap (tabWidth - CAP_WIDTH).
|
|
28329
28299
|
var pillX = React__default.useRef(new Animated.Value(0)).current;
|
|
28330
28300
|
var pillBodyScaleX = React__default.useRef(new Animated.Value(1)).current;
|
|
28331
28301
|
var pillRightOffset = React__default.useRef(new Animated.Value(0)).current;
|
|
@@ -28348,44 +28318,30 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28348
28318
|
if (!layout) return;
|
|
28349
28319
|
(_runningAnimRef$curre = runningAnimRef.current) === null || _runningAnimRef$curre === void 0 || _runningAnimRef$curre.stop();
|
|
28350
28320
|
runningAnimRef.current = null;
|
|
28351
|
-
|
|
28352
|
-
var indicatorScaleXValue = layout.width;
|
|
28353
|
-
// Layer 2 body: width:1 element, scaleX fills space between the two caps.
|
|
28354
|
-
var bodyScaleX = Math.max(layout.width - pillCapWidth * 2, 0);
|
|
28355
|
-
// Layer 2 right cap: offset from pillX to reach the right edge.
|
|
28356
|
-
// Clamped to 0 so the right cap never slides left of the pill's origin
|
|
28357
|
-
// when the tab is narrower than one cap width.
|
|
28358
|
-
var rightOffset = Math.max(layout.width - pillCapWidth, 0);
|
|
28321
|
+
var values = computeValues(layout, pillCapWidth);
|
|
28359
28322
|
if (!animate || !initializedRef.current) {
|
|
28360
|
-
|
|
28361
|
-
|
|
28362
|
-
|
|
28363
|
-
|
|
28364
|
-
|
|
28365
|
-
pillRightOffset.setValue(rightOffset);
|
|
28323
|
+
indicatorX.setValue(values.indicatorX);
|
|
28324
|
+
indicatorScaleX.setValue(values.indicatorScaleX);
|
|
28325
|
+
pillX.setValue(values.pillX);
|
|
28326
|
+
pillBodyScaleX.setValue(values.pillBodyScaleX);
|
|
28327
|
+
pillRightOffset.setValue(values.pillRightOffset);
|
|
28366
28328
|
initializedRef.current = true;
|
|
28367
28329
|
return;
|
|
28368
28330
|
}
|
|
28369
|
-
// All five animations run on the native driver (UI thread):
|
|
28370
|
-
// indicatorX — slides the bottom border
|
|
28371
|
-
// indicatorScaleX — stretches the bottom border
|
|
28372
|
-
// pillX — slides all three pill pieces together
|
|
28373
|
-
// pillBodyScaleX — resizes the body piece to fill between caps
|
|
28374
|
-
// pillRightOffset — keeps the right cap at the pill's right edge
|
|
28375
28331
|
var anim = Animated.parallel([Animated.timing(indicatorX, {
|
|
28376
|
-
toValue:
|
|
28332
|
+
toValue: values.indicatorX,
|
|
28377
28333
|
useNativeDriver: true
|
|
28378
28334
|
}), Animated.timing(indicatorScaleX, {
|
|
28379
|
-
toValue:
|
|
28335
|
+
toValue: values.indicatorScaleX,
|
|
28380
28336
|
useNativeDriver: true
|
|
28381
28337
|
}), Animated.timing(pillX, {
|
|
28382
|
-
toValue:
|
|
28338
|
+
toValue: values.pillX,
|
|
28383
28339
|
useNativeDriver: true
|
|
28384
28340
|
}), Animated.timing(pillBodyScaleX, {
|
|
28385
|
-
toValue:
|
|
28341
|
+
toValue: values.pillBodyScaleX,
|
|
28386
28342
|
useNativeDriver: true
|
|
28387
28343
|
}), Animated.timing(pillRightOffset, {
|
|
28388
|
-
toValue:
|
|
28344
|
+
toValue: values.pillRightOffset,
|
|
28389
28345
|
useNativeDriver: true
|
|
28390
28346
|
})]);
|
|
28391
28347
|
runningAnimRef.current = anim;
|
|
@@ -28400,7 +28356,6 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28400
28356
|
if (layoutsRef.current[selectedIndex]) {
|
|
28401
28357
|
animateTo(selectedIndex, initializedRef.current);
|
|
28402
28358
|
} else {
|
|
28403
|
-
// Layout not yet measured — store as pending and resolve in onTabLayout.
|
|
28404
28359
|
pendingIndexRef.current = selectedIndex;
|
|
28405
28360
|
}
|
|
28406
28361
|
}, [selectedIndex, animateTo]);
|
|
@@ -28412,9 +28367,8 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28412
28367
|
};
|
|
28413
28368
|
}, []);
|
|
28414
28369
|
var onTabLayout = React__default.useCallback(function (index, event) {
|
|
28415
|
-
var
|
|
28416
|
-
|
|
28417
|
-
width = _event$nativeEvent$la.width;
|
|
28370
|
+
var x = PixelRatio.roundToNearestPixel(event.nativeEvent.layout.x);
|
|
28371
|
+
var width = PixelRatio.roundToNearestPixel(event.nativeEvent.layout.width);
|
|
28418
28372
|
var prev = layoutsRef.current[index];
|
|
28419
28373
|
// Skip if layout hasn't meaningfully changed (sub-pixel tolerance).
|
|
28420
28374
|
if (prev && Math.abs(prev.x - x) < 0.5 && Math.abs(prev.width - width) < 0.5) {
|
|
@@ -28424,23 +28378,24 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28424
28378
|
x: x,
|
|
28425
28379
|
width: width
|
|
28426
28380
|
};
|
|
28427
|
-
// Animate if this tab is the selected one (covers the pending case where
|
|
28428
|
-
// selectedIndex was set before the layout was measured).
|
|
28429
28381
|
if (index === selectedIndex || index === pendingIndexRef.current) {
|
|
28430
28382
|
if (index === pendingIndexRef.current) pendingIndexRef.current = undefined;
|
|
28431
28383
|
animateTo(index, initializedRef.current);
|
|
28432
28384
|
}
|
|
28433
|
-
// If no tab is selected yet, snap indicators to tab 0 on its first
|
|
28434
|
-
// layout so they appear at a sensible default position.
|
|
28385
|
+
// If no tab is selected yet, snap indicators to tab 0 on its first layout.
|
|
28435
28386
|
if (!initializedRef.current && index === 0 && selectedIndex === undefined) {
|
|
28436
|
-
|
|
28437
|
-
|
|
28438
|
-
|
|
28439
|
-
|
|
28387
|
+
var values = computeValues({
|
|
28388
|
+
x: x,
|
|
28389
|
+
width: width
|
|
28390
|
+
}, pillCapWidth);
|
|
28391
|
+
indicatorX.setValue(values.indicatorX);
|
|
28392
|
+
indicatorScaleX.setValue(values.indicatorScaleX);
|
|
28393
|
+
pillX.setValue(values.pillX);
|
|
28394
|
+
pillBodyScaleX.setValue(values.pillBodyScaleX);
|
|
28395
|
+
pillRightOffset.setValue(values.pillRightOffset);
|
|
28440
28396
|
initializedRef.current = true;
|
|
28441
28397
|
}
|
|
28442
|
-
}, [animateTo,
|
|
28443
|
-
// Layer 1: transformOrigin 'left center' pins scaleX expansion to left edge.
|
|
28398
|
+
}, [selectedIndex, animateTo, pillCapWidth, indicatorX, indicatorScaleX, pillX, pillBodyScaleX, pillRightOffset]);
|
|
28444
28399
|
var indicatorStyle = {
|
|
28445
28400
|
transformOrigin: 'left center',
|
|
28446
28401
|
transform: [{
|
|
@@ -28449,8 +28404,6 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28449
28404
|
scaleX: indicatorScaleX
|
|
28450
28405
|
}]
|
|
28451
28406
|
};
|
|
28452
|
-
// Layer 2: three pieces, all absolutely positioned, all native driver.
|
|
28453
|
-
// Animated.add computes derived positions without creating JS-driver nodes.
|
|
28454
28407
|
var pillLeftStyle = {
|
|
28455
28408
|
transform: [{
|
|
28456
28409
|
translateX: pillX
|
package/lib/index.js
CHANGED
|
@@ -7845,7 +7845,7 @@ var getFilterTriggerTheme = function getFilterTriggerTheme(theme) {
|
|
|
7845
7845
|
},
|
|
7846
7846
|
inactive: {
|
|
7847
7847
|
filled: theme.colors.neutralGlobalSurface,
|
|
7848
|
-
outlined: theme.colors.
|
|
7848
|
+
outlined: theme.colors.secondaryOutline,
|
|
7849
7849
|
ghost: 'transparent'
|
|
7850
7850
|
}
|
|
7851
7851
|
}
|
|
@@ -28186,7 +28186,7 @@ var HeaderTabPillBody = index$c(reactNative.Animated.View)(function (_ref4) {
|
|
|
28186
28186
|
top: 0,
|
|
28187
28187
|
bottom: 0,
|
|
28188
28188
|
left: 0,
|
|
28189
|
-
width: 1,
|
|
28189
|
+
width: 1 / reactNative.PixelRatio.get(),
|
|
28190
28190
|
backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground
|
|
28191
28191
|
};
|
|
28192
28192
|
});
|
|
@@ -28207,7 +28207,7 @@ var HeaderTabItemActiveBorder = index$c(reactNative.Animated.View)(function (_re
|
|
|
28207
28207
|
return {
|
|
28208
28208
|
position: 'absolute',
|
|
28209
28209
|
bottom: 0,
|
|
28210
|
-
width: 1,
|
|
28210
|
+
width: 1 / reactNative.PixelRatio.get(),
|
|
28211
28211
|
height: theme.__hd__.tabs.borderWidths.highlightedActiveBorder,
|
|
28212
28212
|
backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBorder
|
|
28213
28213
|
};
|
|
@@ -28225,7 +28225,7 @@ var HeaderTabItemWrapper = index$c(reactNative.View)(function (_ref7) {
|
|
|
28225
28225
|
var HeaderTabItemIndicator = index$c(reactNative.Animated.View)(function (_ref8) {
|
|
28226
28226
|
var theme = _ref8.theme;
|
|
28227
28227
|
return {
|
|
28228
|
-
width: 1,
|
|
28228
|
+
width: 1 / reactNative.PixelRatio.get(),
|
|
28229
28229
|
height: theme.__hd__.tabs.sizes.indicator,
|
|
28230
28230
|
position: 'absolute',
|
|
28231
28231
|
bottom: theme.__hd__.tabs.space.tabIndicatorBottom,
|
|
@@ -28306,44 +28306,17 @@ var TabWithBadge = function TabWithBadge(_ref) {
|
|
|
28306
28306
|
return /*#__PURE__*/React__namespace.default.createElement(reactNative.View, null, tabItem);
|
|
28307
28307
|
};
|
|
28308
28308
|
|
|
28309
|
-
/**
|
|
28310
|
-
|
|
28311
|
-
|
|
28312
|
-
|
|
28313
|
-
|
|
28314
|
-
|
|
28315
|
-
|
|
28316
|
-
|
|
28317
|
-
|
|
28318
|
-
|
|
28319
|
-
|
|
28320
|
-
* Layer 2 — pill background (pillLeftStyle / pillBodyStyle / pillRightStyle)
|
|
28321
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
28322
|
-
* The pill is split into three absolutely-positioned children so that
|
|
28323
|
-
* border-radius is never distorted by scale:
|
|
28324
|
-
*
|
|
28325
|
-
* ┌──────────────────────────────────────────────────────┐
|
|
28326
|
-
* │ [cap-left 8px] [body width-1 + scaleX] [cap-right 8px] │
|
|
28327
|
-
* └──────────────────────────────────────────────────────┘
|
|
28328
|
-
*
|
|
28329
|
-
* cap-left — fixed 8px wide, borderTopLeftRadius:8, translateX = pillX
|
|
28330
|
-
* body — width:1 + scaleX trick (scaleX = tabWidth - 16),
|
|
28331
|
-
* transformOrigin 'left center',
|
|
28332
|
-
* translateX = pillX + 8 (via Animated.add)
|
|
28333
|
-
* cap-right — fixed 8px wide, borderTopRightRadius:8,
|
|
28334
|
-
* translateX = pillX + tabWidth - 8 (via Animated.add)
|
|
28335
|
-
*
|
|
28336
|
-
* All four animated values use the native driver (translateX and scaleX are
|
|
28337
|
-
* transform properties). `width` is never animated, so no JS driver needed.
|
|
28338
|
-
*
|
|
28339
|
-
* Driver summary:
|
|
28340
|
-
* indicatorX native translateX — slides the bottom border
|
|
28341
|
-
* indicatorScaleX native scaleX — stretches the bottom border
|
|
28342
|
-
* pillX native translateX — slides all three pill pieces
|
|
28343
|
-
* pillBodyScaleX native scaleX — stretches the pill body
|
|
28344
|
-
* pillRightOffsetX native translateX — positions the right cap
|
|
28345
|
-
* (Animated.add: pillX + tabWidth - 8)
|
|
28346
|
-
*/
|
|
28309
|
+
/** Centralised calculation for all animated values given a tab layout. */
|
|
28310
|
+
var computeValues = function computeValues(layout, pillCapWidth) {
|
|
28311
|
+
var pr = reactNative.PixelRatio.get();
|
|
28312
|
+
return {
|
|
28313
|
+
indicatorX: layout.x,
|
|
28314
|
+
indicatorScaleX: layout.width * pr,
|
|
28315
|
+
pillX: layout.x,
|
|
28316
|
+
pillBodyScaleX: Math.max(layout.width - pillCapWidth * 2, 0) * pr,
|
|
28317
|
+
pillRightOffset: Math.max(layout.width - pillCapWidth, 0)
|
|
28318
|
+
};
|
|
28319
|
+
};
|
|
28347
28320
|
var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
28348
28321
|
var selectedIndex = _ref.selectedIndex,
|
|
28349
28322
|
tabsLength = _ref.tabsLength,
|
|
@@ -28352,9 +28325,6 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28352
28325
|
var indicatorX = React__namespace.default.useRef(new reactNative.Animated.Value(0)).current;
|
|
28353
28326
|
var indicatorScaleX = React__namespace.default.useRef(new reactNative.Animated.Value(1)).current;
|
|
28354
28327
|
// Layer 2 — native driver (pill background, three-piece split).
|
|
28355
|
-
// pillX: left edge of the pill (shared by all three pieces as base).
|
|
28356
|
-
// pillBodyScaleX: scaleX for the body piece (tabWidth - 2 * CAP_WIDTH).
|
|
28357
|
-
// pillRightOffset: additional x offset for the right cap (tabWidth - CAP_WIDTH).
|
|
28358
28328
|
var pillX = React__namespace.default.useRef(new reactNative.Animated.Value(0)).current;
|
|
28359
28329
|
var pillBodyScaleX = React__namespace.default.useRef(new reactNative.Animated.Value(1)).current;
|
|
28360
28330
|
var pillRightOffset = React__namespace.default.useRef(new reactNative.Animated.Value(0)).current;
|
|
@@ -28377,44 +28347,30 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28377
28347
|
if (!layout) return;
|
|
28378
28348
|
(_runningAnimRef$curre = runningAnimRef.current) === null || _runningAnimRef$curre === void 0 || _runningAnimRef$curre.stop();
|
|
28379
28349
|
runningAnimRef.current = null;
|
|
28380
|
-
|
|
28381
|
-
var indicatorScaleXValue = layout.width;
|
|
28382
|
-
// Layer 2 body: width:1 element, scaleX fills space between the two caps.
|
|
28383
|
-
var bodyScaleX = Math.max(layout.width - pillCapWidth * 2, 0);
|
|
28384
|
-
// Layer 2 right cap: offset from pillX to reach the right edge.
|
|
28385
|
-
// Clamped to 0 so the right cap never slides left of the pill's origin
|
|
28386
|
-
// when the tab is narrower than one cap width.
|
|
28387
|
-
var rightOffset = Math.max(layout.width - pillCapWidth, 0);
|
|
28350
|
+
var values = computeValues(layout, pillCapWidth);
|
|
28388
28351
|
if (!animate || !initializedRef.current) {
|
|
28389
|
-
|
|
28390
|
-
|
|
28391
|
-
|
|
28392
|
-
|
|
28393
|
-
|
|
28394
|
-
pillRightOffset.setValue(rightOffset);
|
|
28352
|
+
indicatorX.setValue(values.indicatorX);
|
|
28353
|
+
indicatorScaleX.setValue(values.indicatorScaleX);
|
|
28354
|
+
pillX.setValue(values.pillX);
|
|
28355
|
+
pillBodyScaleX.setValue(values.pillBodyScaleX);
|
|
28356
|
+
pillRightOffset.setValue(values.pillRightOffset);
|
|
28395
28357
|
initializedRef.current = true;
|
|
28396
28358
|
return;
|
|
28397
28359
|
}
|
|
28398
|
-
// All five animations run on the native driver (UI thread):
|
|
28399
|
-
// indicatorX — slides the bottom border
|
|
28400
|
-
// indicatorScaleX — stretches the bottom border
|
|
28401
|
-
// pillX — slides all three pill pieces together
|
|
28402
|
-
// pillBodyScaleX — resizes the body piece to fill between caps
|
|
28403
|
-
// pillRightOffset — keeps the right cap at the pill's right edge
|
|
28404
28360
|
var anim = reactNative.Animated.parallel([reactNative.Animated.timing(indicatorX, {
|
|
28405
|
-
toValue:
|
|
28361
|
+
toValue: values.indicatorX,
|
|
28406
28362
|
useNativeDriver: true
|
|
28407
28363
|
}), reactNative.Animated.timing(indicatorScaleX, {
|
|
28408
|
-
toValue:
|
|
28364
|
+
toValue: values.indicatorScaleX,
|
|
28409
28365
|
useNativeDriver: true
|
|
28410
28366
|
}), reactNative.Animated.timing(pillX, {
|
|
28411
|
-
toValue:
|
|
28367
|
+
toValue: values.pillX,
|
|
28412
28368
|
useNativeDriver: true
|
|
28413
28369
|
}), reactNative.Animated.timing(pillBodyScaleX, {
|
|
28414
|
-
toValue:
|
|
28370
|
+
toValue: values.pillBodyScaleX,
|
|
28415
28371
|
useNativeDriver: true
|
|
28416
28372
|
}), reactNative.Animated.timing(pillRightOffset, {
|
|
28417
|
-
toValue:
|
|
28373
|
+
toValue: values.pillRightOffset,
|
|
28418
28374
|
useNativeDriver: true
|
|
28419
28375
|
})]);
|
|
28420
28376
|
runningAnimRef.current = anim;
|
|
@@ -28429,7 +28385,6 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28429
28385
|
if (layoutsRef.current[selectedIndex]) {
|
|
28430
28386
|
animateTo(selectedIndex, initializedRef.current);
|
|
28431
28387
|
} else {
|
|
28432
|
-
// Layout not yet measured — store as pending and resolve in onTabLayout.
|
|
28433
28388
|
pendingIndexRef.current = selectedIndex;
|
|
28434
28389
|
}
|
|
28435
28390
|
}, [selectedIndex, animateTo]);
|
|
@@ -28441,9 +28396,8 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28441
28396
|
};
|
|
28442
28397
|
}, []);
|
|
28443
28398
|
var onTabLayout = React__namespace.default.useCallback(function (index, event) {
|
|
28444
|
-
var
|
|
28445
|
-
|
|
28446
|
-
width = _event$nativeEvent$la.width;
|
|
28399
|
+
var x = reactNative.PixelRatio.roundToNearestPixel(event.nativeEvent.layout.x);
|
|
28400
|
+
var width = reactNative.PixelRatio.roundToNearestPixel(event.nativeEvent.layout.width);
|
|
28447
28401
|
var prev = layoutsRef.current[index];
|
|
28448
28402
|
// Skip if layout hasn't meaningfully changed (sub-pixel tolerance).
|
|
28449
28403
|
if (prev && Math.abs(prev.x - x) < 0.5 && Math.abs(prev.width - width) < 0.5) {
|
|
@@ -28453,23 +28407,24 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28453
28407
|
x: x,
|
|
28454
28408
|
width: width
|
|
28455
28409
|
};
|
|
28456
|
-
// Animate if this tab is the selected one (covers the pending case where
|
|
28457
|
-
// selectedIndex was set before the layout was measured).
|
|
28458
28410
|
if (index === selectedIndex || index === pendingIndexRef.current) {
|
|
28459
28411
|
if (index === pendingIndexRef.current) pendingIndexRef.current = undefined;
|
|
28460
28412
|
animateTo(index, initializedRef.current);
|
|
28461
28413
|
}
|
|
28462
|
-
// If no tab is selected yet, snap indicators to tab 0 on its first
|
|
28463
|
-
// layout so they appear at a sensible default position.
|
|
28414
|
+
// If no tab is selected yet, snap indicators to tab 0 on its first layout.
|
|
28464
28415
|
if (!initializedRef.current && index === 0 && selectedIndex === undefined) {
|
|
28465
|
-
|
|
28466
|
-
|
|
28467
|
-
|
|
28468
|
-
|
|
28416
|
+
var values = computeValues({
|
|
28417
|
+
x: x,
|
|
28418
|
+
width: width
|
|
28419
|
+
}, pillCapWidth);
|
|
28420
|
+
indicatorX.setValue(values.indicatorX);
|
|
28421
|
+
indicatorScaleX.setValue(values.indicatorScaleX);
|
|
28422
|
+
pillX.setValue(values.pillX);
|
|
28423
|
+
pillBodyScaleX.setValue(values.pillBodyScaleX);
|
|
28424
|
+
pillRightOffset.setValue(values.pillRightOffset);
|
|
28469
28425
|
initializedRef.current = true;
|
|
28470
28426
|
}
|
|
28471
|
-
}, [animateTo,
|
|
28472
|
-
// Layer 1: transformOrigin 'left center' pins scaleX expansion to left edge.
|
|
28427
|
+
}, [selectedIndex, animateTo, pillCapWidth, indicatorX, indicatorScaleX, pillX, pillBodyScaleX, pillRightOffset]);
|
|
28473
28428
|
var indicatorStyle = {
|
|
28474
28429
|
transformOrigin: 'left center',
|
|
28475
28430
|
transform: [{
|
|
@@ -28478,8 +28433,6 @@ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
|
|
|
28478
28433
|
scaleX: indicatorScaleX
|
|
28479
28434
|
}]
|
|
28480
28435
|
};
|
|
28481
|
-
// Layer 2: three pieces, all absolutely positioned, all native driver.
|
|
28482
|
-
// Animated.add computes derived positions without creating JS-driver nodes.
|
|
28483
28436
|
var pillLeftStyle = {
|
|
28484
28437
|
transform: [{
|
|
28485
28438
|
translateX: pillX
|
package/package.json
CHANGED
|
@@ -12,8 +12,6 @@ export type IconName = typeof IconList[number];
|
|
|
12
12
|
export interface IconProps extends AccessibilityProps {
|
|
13
13
|
/**
|
|
14
14
|
* Name of the Icon.
|
|
15
|
-
*
|
|
16
|
-
* icon['carat-*'] - @deprecated Icons starting with 'carat' are deprecated and will be removed in the next major release, please use 'caret' instead.
|
|
17
15
|
*/
|
|
18
16
|
icon: IconName;
|
|
19
17
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Animated } from 'react-native';
|
|
2
|
+
import { Animated, PixelRatio } from 'react-native';
|
|
3
3
|
import type { LayoutChangeEvent } from 'react-native';
|
|
4
4
|
|
|
5
5
|
type Layout = { x: number; width: number };
|
|
@@ -9,9 +9,9 @@ type Layout = { x: number; width: number };
|
|
|
9
9
|
*
|
|
10
10
|
* Layer 1 — bottom border / underline (indicatorStyle)
|
|
11
11
|
* ─────────────────────────────────────────────────────
|
|
12
|
-
* Uses the "width:1 + scaleX" trick: the element has a
|
|
13
|
-
* width of
|
|
14
|
-
*
|
|
12
|
+
* Uses the "width:1/PixelRatio + scaleX" trick: the element has a stylesheet
|
|
13
|
+
* width of 1 physical pixel (1/PixelRatio dp) and scaleX is set to
|
|
14
|
+
* targetWidth * PixelRatio, giving a visual width of targetWidth dp.
|
|
15
15
|
* Both translateX and scaleX are transform properties → native driver.
|
|
16
16
|
* Caveat: scaleX also scales border-radius, so this layer has no border-radius.
|
|
17
17
|
*
|
|
@@ -21,27 +21,43 @@ type Layout = { x: number; width: number };
|
|
|
21
21
|
* border-radius is never distorted by scale:
|
|
22
22
|
*
|
|
23
23
|
* ┌──────────────────────────────────────────────────────┐
|
|
24
|
-
* │ [cap-left
|
|
24
|
+
* │ [cap-left cap] [body 1px + scaleX] [cap-right cap] │
|
|
25
25
|
* └──────────────────────────────────────────────────────┘
|
|
26
26
|
*
|
|
27
|
-
* cap-left — fixed
|
|
28
|
-
* body —
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* translateX = pillX + tabWidth - 8 (via Animated.add)
|
|
33
|
-
*
|
|
34
|
-
* All four animated values use the native driver (translateX and scaleX are
|
|
35
|
-
* transform properties). `width` is never animated, so no JS driver needed.
|
|
27
|
+
* cap-left — fixed pillCapWidth dp, borderTopLeftRadius, translateX = pillX
|
|
28
|
+
* body — 1px + scaleX trick (scaleX = (tabWidth - 2*cap) * PixelRatio),
|
|
29
|
+
* translateX = pillX + pillCapWidth (via Animated.add)
|
|
30
|
+
* cap-right — fixed pillCapWidth dp, borderTopRightRadius,
|
|
31
|
+
* translateX = pillX + rightOffset (via Animated.add)
|
|
36
32
|
*
|
|
37
33
|
* Driver summary:
|
|
38
|
-
* indicatorX
|
|
39
|
-
* indicatorScaleX
|
|
40
|
-
* pillX
|
|
41
|
-
* pillBodyScaleX
|
|
42
|
-
*
|
|
43
|
-
* (Animated.add: pillX + tabWidth - 8)
|
|
34
|
+
* indicatorX native translateX — slides the bottom border
|
|
35
|
+
* indicatorScaleX native scaleX — stretches the bottom border
|
|
36
|
+
* pillX native translateX — slides all three pill pieces
|
|
37
|
+
* pillBodyScaleX native scaleX — stretches the pill body
|
|
38
|
+
* pillRightOffset native translateX — positions the right cap
|
|
44
39
|
*/
|
|
40
|
+
|
|
41
|
+
type AnimValues = {
|
|
42
|
+
indicatorX: number;
|
|
43
|
+
indicatorScaleX: number;
|
|
44
|
+
pillX: number;
|
|
45
|
+
pillBodyScaleX: number;
|
|
46
|
+
pillRightOffset: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Centralised calculation for all animated values given a tab layout. */
|
|
50
|
+
const computeValues = (layout: Layout, pillCapWidth: number): AnimValues => {
|
|
51
|
+
const pr = PixelRatio.get();
|
|
52
|
+
return {
|
|
53
|
+
indicatorX: layout.x,
|
|
54
|
+
indicatorScaleX: layout.width * pr,
|
|
55
|
+
pillX: layout.x,
|
|
56
|
+
pillBodyScaleX: Math.max(layout.width - pillCapWidth * 2, 0) * pr,
|
|
57
|
+
pillRightOffset: Math.max(layout.width - pillCapWidth, 0),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
45
61
|
const useIndicatorAnimation = ({
|
|
46
62
|
selectedIndex,
|
|
47
63
|
tabsLength,
|
|
@@ -57,9 +73,6 @@ const useIndicatorAnimation = ({
|
|
|
57
73
|
const indicatorScaleX = React.useRef(new Animated.Value(1)).current;
|
|
58
74
|
|
|
59
75
|
// Layer 2 — native driver (pill background, three-piece split).
|
|
60
|
-
// pillX: left edge of the pill (shared by all three pieces as base).
|
|
61
|
-
// pillBodyScaleX: scaleX for the body piece (tabWidth - 2 * CAP_WIDTH).
|
|
62
|
-
// pillRightOffset: additional x offset for the right cap (tabWidth - CAP_WIDTH).
|
|
63
76
|
const pillX = React.useRef(new Animated.Value(0)).current;
|
|
64
77
|
const pillBodyScaleX = React.useRef(new Animated.Value(1)).current;
|
|
65
78
|
const pillRightOffset = React.useRef(new Animated.Value(0)).current;
|
|
@@ -86,51 +99,37 @@ const useIndicatorAnimation = ({
|
|
|
86
99
|
runningAnimRef.current?.stop();
|
|
87
100
|
runningAnimRef.current = null;
|
|
88
101
|
|
|
89
|
-
|
|
90
|
-
const indicatorScaleXValue = layout.width;
|
|
91
|
-
// Layer 2 body: width:1 element, scaleX fills space between the two caps.
|
|
92
|
-
const bodyScaleX = Math.max(layout.width - pillCapWidth * 2, 0);
|
|
93
|
-
// Layer 2 right cap: offset from pillX to reach the right edge.
|
|
94
|
-
// Clamped to 0 so the right cap never slides left of the pill's origin
|
|
95
|
-
// when the tab is narrower than one cap width.
|
|
96
|
-
const rightOffset = Math.max(layout.width - pillCapWidth, 0);
|
|
102
|
+
const values = computeValues(layout, pillCapWidth);
|
|
97
103
|
|
|
98
104
|
if (!animate || !initializedRef.current) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
pillRightOffset.setValue(rightOffset);
|
|
105
|
+
indicatorX.setValue(values.indicatorX);
|
|
106
|
+
indicatorScaleX.setValue(values.indicatorScaleX);
|
|
107
|
+
pillX.setValue(values.pillX);
|
|
108
|
+
pillBodyScaleX.setValue(values.pillBodyScaleX);
|
|
109
|
+
pillRightOffset.setValue(values.pillRightOffset);
|
|
105
110
|
initializedRef.current = true;
|
|
106
111
|
return;
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
// All five animations run on the native driver (UI thread):
|
|
110
|
-
// indicatorX — slides the bottom border
|
|
111
|
-
// indicatorScaleX — stretches the bottom border
|
|
112
|
-
// pillX — slides all three pill pieces together
|
|
113
|
-
// pillBodyScaleX — resizes the body piece to fill between caps
|
|
114
|
-
// pillRightOffset — keeps the right cap at the pill's right edge
|
|
115
114
|
const anim = Animated.parallel([
|
|
116
115
|
Animated.timing(indicatorX, {
|
|
117
|
-
toValue:
|
|
116
|
+
toValue: values.indicatorX,
|
|
118
117
|
useNativeDriver: true,
|
|
119
118
|
}),
|
|
120
119
|
Animated.timing(indicatorScaleX, {
|
|
121
|
-
toValue:
|
|
120
|
+
toValue: values.indicatorScaleX,
|
|
122
121
|
useNativeDriver: true,
|
|
123
122
|
}),
|
|
124
123
|
Animated.timing(pillX, {
|
|
125
|
-
toValue:
|
|
124
|
+
toValue: values.pillX,
|
|
126
125
|
useNativeDriver: true,
|
|
127
126
|
}),
|
|
128
127
|
Animated.timing(pillBodyScaleX, {
|
|
129
|
-
toValue:
|
|
128
|
+
toValue: values.pillBodyScaleX,
|
|
130
129
|
useNativeDriver: true,
|
|
131
130
|
}),
|
|
132
131
|
Animated.timing(pillRightOffset, {
|
|
133
|
-
toValue:
|
|
132
|
+
toValue: values.pillRightOffset,
|
|
134
133
|
useNativeDriver: true,
|
|
135
134
|
}),
|
|
136
135
|
]);
|
|
@@ -155,7 +154,6 @@ const useIndicatorAnimation = ({
|
|
|
155
154
|
if (layoutsRef.current[selectedIndex]) {
|
|
156
155
|
animateTo(selectedIndex, initializedRef.current);
|
|
157
156
|
} else {
|
|
158
|
-
// Layout not yet measured — store as pending and resolve in onTabLayout.
|
|
159
157
|
pendingIndexRef.current = selectedIndex;
|
|
160
158
|
}
|
|
161
159
|
}, [selectedIndex, animateTo]);
|
|
@@ -169,7 +167,10 @@ const useIndicatorAnimation = ({
|
|
|
169
167
|
|
|
170
168
|
const onTabLayout = React.useCallback(
|
|
171
169
|
(index: number, event: LayoutChangeEvent) => {
|
|
172
|
-
const
|
|
170
|
+
const x = PixelRatio.roundToNearestPixel(event.nativeEvent.layout.x);
|
|
171
|
+
const width = PixelRatio.roundToNearestPixel(
|
|
172
|
+
event.nativeEvent.layout.width
|
|
173
|
+
);
|
|
173
174
|
const prev = layoutsRef.current[index];
|
|
174
175
|
// Skip if layout hasn't meaningfully changed (sub-pixel tolerance).
|
|
175
176
|
if (
|
|
@@ -181,39 +182,44 @@ const useIndicatorAnimation = ({
|
|
|
181
182
|
}
|
|
182
183
|
layoutsRef.current[index] = { x, width };
|
|
183
184
|
|
|
184
|
-
// Animate if this tab is the selected one (covers the pending case where
|
|
185
|
-
// selectedIndex was set before the layout was measured).
|
|
186
185
|
if (index === selectedIndex || index === pendingIndexRef.current) {
|
|
187
186
|
if (index === pendingIndexRef.current)
|
|
188
187
|
pendingIndexRef.current = undefined;
|
|
189
188
|
animateTo(index, initializedRef.current);
|
|
190
189
|
}
|
|
191
190
|
|
|
192
|
-
// If no tab is selected yet, snap indicators to tab 0 on its first
|
|
193
|
-
// layout so they appear at a sensible default position.
|
|
191
|
+
// If no tab is selected yet, snap indicators to tab 0 on its first layout.
|
|
194
192
|
if (
|
|
195
193
|
!initializedRef.current &&
|
|
196
194
|
index === 0 &&
|
|
197
195
|
selectedIndex === undefined
|
|
198
196
|
) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
const values = computeValues({ x, width }, pillCapWidth);
|
|
198
|
+
indicatorX.setValue(values.indicatorX);
|
|
199
|
+
indicatorScaleX.setValue(values.indicatorScaleX);
|
|
200
|
+
pillX.setValue(values.pillX);
|
|
201
|
+
pillBodyScaleX.setValue(values.pillBodyScaleX);
|
|
202
|
+
pillRightOffset.setValue(values.pillRightOffset);
|
|
203
203
|
initializedRef.current = true;
|
|
204
204
|
}
|
|
205
205
|
},
|
|
206
|
-
[
|
|
206
|
+
[
|
|
207
|
+
selectedIndex,
|
|
208
|
+
animateTo,
|
|
209
|
+
pillCapWidth,
|
|
210
|
+
indicatorX,
|
|
211
|
+
indicatorScaleX,
|
|
212
|
+
pillX,
|
|
213
|
+
pillBodyScaleX,
|
|
214
|
+
pillRightOffset,
|
|
215
|
+
]
|
|
207
216
|
);
|
|
208
217
|
|
|
209
|
-
// Layer 1: transformOrigin 'left center' pins scaleX expansion to left edge.
|
|
210
218
|
const indicatorStyle = {
|
|
211
219
|
transformOrigin: 'left center',
|
|
212
220
|
transform: [{ translateX: indicatorX }, { scaleX: indicatorScaleX }],
|
|
213
221
|
} as const;
|
|
214
222
|
|
|
215
|
-
// Layer 2: three pieces, all absolutely positioned, all native driver.
|
|
216
|
-
// Animated.add computes derived positions without creating JS-driver nodes.
|
|
217
223
|
const pillLeftStyle = {
|
|
218
224
|
transform: [{ translateX: pillX }],
|
|
219
225
|
} as const;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Animated, Platform, View } from 'react-native';
|
|
1
|
+
import { Animated, PixelRatio, Platform, View } from 'react-native';
|
|
2
2
|
import styled from '@emotion/native';
|
|
3
3
|
|
|
4
4
|
// Checks if the platform is Android 7x and 8.x, i.e., API levels 24 to 27 (Android 7.0 to 8.1),
|
|
@@ -67,7 +67,7 @@ const HeaderTabPillBody = styled(Animated.View)(({ theme }) => ({
|
|
|
67
67
|
top: 0,
|
|
68
68
|
bottom: 0,
|
|
69
69
|
left: 0,
|
|
70
|
-
width: 1,
|
|
70
|
+
width: 1 / PixelRatio.get(),
|
|
71
71
|
backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground,
|
|
72
72
|
}));
|
|
73
73
|
|
|
@@ -84,7 +84,7 @@ const HeaderTabPillRight = styled(Animated.View)(({ theme }) => ({
|
|
|
84
84
|
const HeaderTabItemActiveBorder = styled(Animated.View)(({ theme }) => ({
|
|
85
85
|
position: 'absolute',
|
|
86
86
|
bottom: 0,
|
|
87
|
-
width: 1,
|
|
87
|
+
width: 1 / PixelRatio.get(),
|
|
88
88
|
height: theme.__hd__.tabs.borderWidths.highlightedActiveBorder,
|
|
89
89
|
backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBorder,
|
|
90
90
|
}));
|
|
@@ -99,7 +99,7 @@ const HeaderTabItemWrapper = styled(View)(({ theme }) => ({
|
|
|
99
99
|
}));
|
|
100
100
|
|
|
101
101
|
const HeaderTabItemIndicator = styled(Animated.View)(({ theme }) => ({
|
|
102
|
-
width: 1,
|
|
102
|
+
width: 1 / PixelRatio.get(),
|
|
103
103
|
height: theme.__hd__.tabs.sizes.indicator,
|
|
104
104
|
position: 'absolute',
|
|
105
105
|
bottom: theme.__hd__.tabs.space.tabIndicatorBottom,
|
|
@@ -5,8 +5,6 @@ export type IconName = typeof IconList[number];
|
|
|
5
5
|
export interface IconProps extends AccessibilityProps {
|
|
6
6
|
/**
|
|
7
7
|
* Name of the Icon.
|
|
8
|
-
*
|
|
9
|
-
* icon['carat-*'] - @deprecated Icons starting with 'carat' are deprecated and will be removed in the next major release, please use 'caret' instead.
|
|
10
8
|
*/
|
|
11
9
|
icon: IconName;
|
|
12
10
|
/**
|
|
@@ -1,43 +1,5 @@
|
|
|
1
1
|
import { Animated } from 'react-native';
|
|
2
2
|
import type { LayoutChangeEvent } from 'react-native';
|
|
3
|
-
/**
|
|
4
|
-
* Drives two visual layers that slide to the selected tab on every press:
|
|
5
|
-
*
|
|
6
|
-
* Layer 1 — bottom border / underline (indicatorStyle)
|
|
7
|
-
* ─────────────────────────────────────────────────────
|
|
8
|
-
* Uses the "width:1 + scaleX" trick: the element has a fixed stylesheet
|
|
9
|
-
* width of 1px and scaleX is set to the target pixel width, giving a visual
|
|
10
|
-
* width of 1 × scaleX pixels without touching any layout property.
|
|
11
|
-
* Both translateX and scaleX are transform properties → native driver.
|
|
12
|
-
* Caveat: scaleX also scales border-radius, so this layer has no border-radius.
|
|
13
|
-
*
|
|
14
|
-
* Layer 2 — pill background (pillLeftStyle / pillBodyStyle / pillRightStyle)
|
|
15
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
-
* The pill is split into three absolutely-positioned children so that
|
|
17
|
-
* border-radius is never distorted by scale:
|
|
18
|
-
*
|
|
19
|
-
* ┌──────────────────────────────────────────────────────┐
|
|
20
|
-
* │ [cap-left 8px] [body width-1 + scaleX] [cap-right 8px] │
|
|
21
|
-
* └──────────────────────────────────────────────────────┘
|
|
22
|
-
*
|
|
23
|
-
* cap-left — fixed 8px wide, borderTopLeftRadius:8, translateX = pillX
|
|
24
|
-
* body — width:1 + scaleX trick (scaleX = tabWidth - 16),
|
|
25
|
-
* transformOrigin 'left center',
|
|
26
|
-
* translateX = pillX + 8 (via Animated.add)
|
|
27
|
-
* cap-right — fixed 8px wide, borderTopRightRadius:8,
|
|
28
|
-
* translateX = pillX + tabWidth - 8 (via Animated.add)
|
|
29
|
-
*
|
|
30
|
-
* All four animated values use the native driver (translateX and scaleX are
|
|
31
|
-
* transform properties). `width` is never animated, so no JS driver needed.
|
|
32
|
-
*
|
|
33
|
-
* Driver summary:
|
|
34
|
-
* indicatorX native translateX — slides the bottom border
|
|
35
|
-
* indicatorScaleX native scaleX — stretches the bottom border
|
|
36
|
-
* pillX native translateX — slides all three pill pieces
|
|
37
|
-
* pillBodyScaleX native scaleX — stretches the pill body
|
|
38
|
-
* pillRightOffsetX native translateX — positions the right cap
|
|
39
|
-
* (Animated.add: pillX + tabWidth - 8)
|
|
40
|
-
*/
|
|
41
3
|
declare const useIndicatorAnimation: ({ selectedIndex, tabsLength, pillCapWidth, }: {
|
|
42
4
|
selectedIndex: number | undefined;
|
|
43
5
|
tabsLength: number;
|