@webority-technologies/mobile 0.0.22 → 0.0.23
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/commonjs/components/Accordion/Accordion.js +4 -2
- package/lib/commonjs/components/Avatar/Avatar.js +4 -2
- package/lib/commonjs/components/Badge/Badge.js +5 -5
- package/lib/commonjs/components/Banner/Banner.js +8 -4
- package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +6 -4
- package/lib/commonjs/components/BottomSheet/BottomSheet.js +8 -9
- package/lib/commonjs/components/Box/Box.js +162 -0
- package/lib/commonjs/components/Box/index.js +37 -0
- package/lib/commonjs/components/Button/Button.js +7 -7
- package/lib/commonjs/components/Carousel/Carousel.js +4 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +14 -5
- package/lib/commonjs/components/DatePicker/DatePicker.js +9 -7
- package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +5 -2
- package/lib/commonjs/components/Dialog/Dialog.js +2 -2
- package/lib/commonjs/components/FieldBase/FieldBase.js +8 -4
- package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +13 -5
- package/lib/commonjs/components/FormField/FormField.js +61 -25
- package/lib/commonjs/components/Input/Input.js +41 -29
- package/lib/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +102 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +13 -0
- package/lib/commonjs/components/KeyboardToolbar/KeyboardToolbar.js +130 -0
- package/lib/commonjs/components/KeyboardToolbar/index.js +13 -0
- package/lib/commonjs/components/Modal/Modal.js +17 -6
- package/lib/commonjs/components/NumberInput/NumberInput.js +35 -28
- package/lib/commonjs/components/OTPInput/OTPInput.js +33 -18
- package/lib/commonjs/components/Radio/Radio.js +7 -5
- package/lib/commonjs/components/Radio/RadioGroup.js +10 -3
- package/lib/commonjs/components/SearchBar/SearchBar.js +4 -2
- package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +20 -10
- package/lib/commonjs/components/Select/Select.js +33 -32
- package/lib/commonjs/components/Skeleton/SkeletonContent.js +5 -2
- package/lib/commonjs/components/Slider/Slider.js +42 -26
- package/lib/commonjs/components/Spinner/Spinner.js +5 -5
- package/lib/commonjs/components/Switch/Switch.js +29 -16
- package/lib/commonjs/components/Tabs/Tabs.js +4 -2
- package/lib/commonjs/components/Text/Text.js +142 -0
- package/lib/commonjs/components/Text/index.js +13 -0
- package/lib/commonjs/components/TimePicker/TimePicker.js +10 -7
- package/lib/commonjs/components/Toast/Toast.js +22 -10
- package/lib/commonjs/components/Tooltip/Tooltip.js +6 -2
- package/lib/commonjs/components/index.js +135 -89
- package/lib/commonjs/form/FormContext.js +40 -0
- package/lib/commonjs/form/index.js +68 -0
- package/lib/commonjs/form/path.js +79 -0
- package/lib/commonjs/form/rules.js +67 -0
- package/lib/commonjs/form/types.js +2 -0
- package/lib/commonjs/form/useField.js +54 -0
- package/lib/commonjs/form/useForm.js +316 -0
- package/lib/commonjs/hooks/index.js +14 -0
- package/lib/commonjs/hooks/useControllableState.js +30 -0
- package/lib/commonjs/hooks/useReducedMotion.js +31 -0
- package/lib/commonjs/index.js +96 -11
- package/lib/commonjs/theme/ThemeContext.js +30 -2
- package/lib/commonjs/theme/tokens.js +12 -0
- package/lib/module/components/Accordion/Accordion.js +4 -2
- package/lib/module/components/Avatar/Avatar.js +4 -2
- package/lib/module/components/Badge/Badge.js +5 -5
- package/lib/module/components/Banner/Banner.js +8 -4
- package/lib/module/components/BottomNavigation/BottomNavigation.js +6 -4
- package/lib/module/components/BottomSheet/BottomSheet.js +8 -9
- package/lib/module/components/Box/Box.js +156 -0
- package/lib/module/components/Box/index.js +4 -0
- package/lib/module/components/Button/Button.js +7 -7
- package/lib/module/components/Carousel/Carousel.js +4 -2
- package/lib/module/components/Checkbox/Checkbox.js +14 -5
- package/lib/module/components/DatePicker/DatePicker.js +9 -7
- package/lib/module/components/DateRangePicker/DateRangePicker.js +5 -2
- package/lib/module/components/Dialog/Dialog.js +2 -2
- package/lib/module/components/FieldBase/FieldBase.js +8 -4
- package/lib/module/components/FloatingActionButton/FloatingActionButton.js +13 -5
- package/lib/module/components/FormField/FormField.js +62 -26
- package/lib/module/components/Input/Input.js +41 -29
- package/lib/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +98 -0
- package/lib/module/components/KeyboardAwareScrollView/index.js +4 -0
- package/lib/module/components/KeyboardToolbar/KeyboardToolbar.js +125 -0
- package/lib/module/components/KeyboardToolbar/index.js +4 -0
- package/lib/module/components/Modal/Modal.js +17 -6
- package/lib/module/components/NumberInput/NumberInput.js +30 -23
- package/lib/module/components/OTPInput/OTPInput.js +30 -15
- package/lib/module/components/Radio/Radio.js +7 -5
- package/lib/module/components/Radio/RadioGroup.js +10 -3
- package/lib/module/components/SearchBar/SearchBar.js +4 -2
- package/lib/module/components/SegmentedControl/SegmentedControl.js +20 -10
- package/lib/module/components/Select/Select.js +33 -32
- package/lib/module/components/Skeleton/SkeletonContent.js +5 -2
- package/lib/module/components/Slider/Slider.js +42 -26
- package/lib/module/components/Spinner/Spinner.js +5 -5
- package/lib/module/components/Switch/Switch.js +29 -16
- package/lib/module/components/Tabs/Tabs.js +4 -2
- package/lib/module/components/Text/Text.js +138 -0
- package/lib/module/components/Text/index.js +4 -0
- package/lib/module/components/TimePicker/TimePicker.js +10 -7
- package/lib/module/components/Toast/Toast.js +22 -10
- package/lib/module/components/Tooltip/Tooltip.js +6 -2
- package/lib/module/components/index.js +4 -0
- package/lib/module/form/FormContext.js +32 -0
- package/lib/module/form/index.js +12 -0
- package/lib/module/form/path.js +72 -0
- package/lib/module/form/rules.js +52 -0
- package/lib/module/form/types.js +2 -0
- package/lib/module/form/useField.js +49 -0
- package/lib/module/form/useForm.js +312 -0
- package/lib/module/hooks/index.js +2 -0
- package/lib/module/hooks/useControllableState.js +26 -0
- package/lib/module/hooks/useReducedMotion.js +27 -0
- package/lib/module/index.js +3 -1
- package/lib/module/theme/ThemeContext.js +30 -2
- package/lib/module/theme/tokens.js +12 -0
- package/lib/typescript/commonjs/components/BottomNavigation/BottomNavigation.d.ts +1 -1
- package/lib/typescript/commonjs/components/Box/Box.d.ts +60 -0
- package/lib/typescript/commonjs/components/Box/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/Button/Button.d.ts +1 -1
- package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +3 -3
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +2 -2
- package/lib/typescript/commonjs/components/FormField/FormField.d.ts +13 -2
- package/lib/typescript/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
- package/lib/typescript/commonjs/components/KeyboardAwareScrollView/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
- package/lib/typescript/commonjs/components/KeyboardToolbar/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -2
- package/lib/typescript/commonjs/components/OTPInput/OTPInput.d.ts +3 -2
- package/lib/typescript/commonjs/components/Radio/Radio.d.ts +2 -2
- package/lib/typescript/commonjs/components/Radio/RadioGroup.d.ts +3 -2
- package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -2
- package/lib/typescript/commonjs/components/Slider/Slider.d.ts +6 -4
- package/lib/typescript/commonjs/components/Spinner/Spinner.d.ts +1 -1
- package/lib/typescript/commonjs/components/Switch/Switch.d.ts +3 -2
- package/lib/typescript/commonjs/components/Text/Text.d.ts +25 -0
- package/lib/typescript/commonjs/components/Text/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +3 -3
- package/lib/typescript/commonjs/components/index.d.ts +8 -0
- package/lib/typescript/commonjs/form/FormContext.d.ts +17 -0
- package/lib/typescript/commonjs/form/index.d.ts +9 -0
- package/lib/typescript/commonjs/form/path.d.ts +10 -0
- package/lib/typescript/commonjs/form/rules.d.ts +31 -0
- package/lib/typescript/commonjs/form/types.d.ts +94 -0
- package/lib/typescript/commonjs/form/useField.d.ts +27 -0
- package/lib/typescript/commonjs/form/useForm.d.ts +10 -0
- package/lib/typescript/commonjs/hooks/index.d.ts +3 -0
- package/lib/typescript/commonjs/hooks/useControllableState.d.ts +17 -0
- package/lib/typescript/commonjs/hooks/useReducedMotion.d.ts +8 -0
- package/lib/typescript/commonjs/index.d.ts +4 -2
- package/lib/typescript/commonjs/theme/types.d.ts +15 -0
- package/lib/typescript/module/components/BottomNavigation/BottomNavigation.d.ts +1 -1
- package/lib/typescript/module/components/Box/Box.d.ts +60 -0
- package/lib/typescript/module/components/Box/index.d.ts +3 -0
- package/lib/typescript/module/components/Button/Button.d.ts +1 -1
- package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +3 -3
- package/lib/typescript/module/components/Dialog/Dialog.d.ts +2 -2
- package/lib/typescript/module/components/FormField/FormField.d.ts +13 -2
- package/lib/typescript/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
- package/lib/typescript/module/components/KeyboardAwareScrollView/index.d.ts +3 -0
- package/lib/typescript/module/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
- package/lib/typescript/module/components/KeyboardToolbar/index.d.ts +3 -0
- package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -2
- package/lib/typescript/module/components/OTPInput/OTPInput.d.ts +3 -2
- package/lib/typescript/module/components/Radio/Radio.d.ts +2 -2
- package/lib/typescript/module/components/Radio/RadioGroup.d.ts +3 -2
- package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -2
- package/lib/typescript/module/components/Slider/Slider.d.ts +6 -4
- package/lib/typescript/module/components/Spinner/Spinner.d.ts +1 -1
- package/lib/typescript/module/components/Switch/Switch.d.ts +3 -2
- package/lib/typescript/module/components/Text/Text.d.ts +25 -0
- package/lib/typescript/module/components/Text/index.d.ts +3 -0
- package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +3 -3
- package/lib/typescript/module/components/index.d.ts +8 -0
- package/lib/typescript/module/form/FormContext.d.ts +17 -0
- package/lib/typescript/module/form/index.d.ts +9 -0
- package/lib/typescript/module/form/path.d.ts +10 -0
- package/lib/typescript/module/form/rules.d.ts +31 -0
- package/lib/typescript/module/form/types.d.ts +94 -0
- package/lib/typescript/module/form/useField.d.ts +27 -0
- package/lib/typescript/module/form/useForm.d.ts +10 -0
- package/lib/typescript/module/hooks/index.d.ts +3 -0
- package/lib/typescript/module/hooks/useControllableState.d.ts +17 -0
- package/lib/typescript/module/hooks/useReducedMotion.d.ts +8 -0
- package/lib/typescript/module/index.d.ts +4 -2
- package/lib/typescript/module/theme/types.d.ts +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { Keyboard, Platform, Pressable, StyleSheet, View } from 'react-native';
|
|
5
|
+
import { useTheme } from "../../theme/index.js";
|
|
6
|
+
import { useOptionalFormContext } from "../../form/FormContext.js";
|
|
7
|
+
import { Text } from "../Text/index.js";
|
|
8
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
+
const NavButton = ({
|
|
10
|
+
label,
|
|
11
|
+
accessibilityLabel,
|
|
12
|
+
onPress,
|
|
13
|
+
theme
|
|
14
|
+
}) => /*#__PURE__*/_jsx(Pressable, {
|
|
15
|
+
onPress: onPress,
|
|
16
|
+
disabled: !onPress,
|
|
17
|
+
accessibilityRole: "button",
|
|
18
|
+
accessibilityLabel: accessibilityLabel,
|
|
19
|
+
hitSlop: 8,
|
|
20
|
+
style: {
|
|
21
|
+
paddingHorizontal: theme.spacing.sm,
|
|
22
|
+
opacity: onPress ? 1 : 0.4
|
|
23
|
+
},
|
|
24
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
25
|
+
variant: "h2",
|
|
26
|
+
color: onPress ? 'link' : 'disabled',
|
|
27
|
+
children: label
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* An accessory bar pinned just above the keyboard — pure RN, no native
|
|
33
|
+
* dependency. Shows a Done (dismiss) button and optional prev/next chevrons
|
|
34
|
+
* that, inside a `<Form>`, walk the registered field order. Render it at the
|
|
35
|
+
* screen root (a `flex: 1` ancestor) so its `bottom: keyboardHeight` offset
|
|
36
|
+
* lands it on top of the keyboard. Renders nothing while the keyboard is hidden.
|
|
37
|
+
*/
|
|
38
|
+
const KeyboardToolbar = props => {
|
|
39
|
+
const {
|
|
40
|
+
doneLabel = 'Done',
|
|
41
|
+
showNavigation,
|
|
42
|
+
onDone,
|
|
43
|
+
onNext,
|
|
44
|
+
onPrev,
|
|
45
|
+
leading,
|
|
46
|
+
style,
|
|
47
|
+
testID
|
|
48
|
+
} = props;
|
|
49
|
+
const theme = useTheme();
|
|
50
|
+
const form = useOptionalFormContext();
|
|
51
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
54
|
+
const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
55
|
+
const showSub = Keyboard.addListener(showEvent, e => setKeyboardHeight(e.endCoordinates?.height ?? 0));
|
|
56
|
+
const hideSub = Keyboard.addListener(hideEvent, () => setKeyboardHeight(0));
|
|
57
|
+
return () => {
|
|
58
|
+
showSub.remove();
|
|
59
|
+
hideSub.remove();
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
if (keyboardHeight <= 0) return null;
|
|
63
|
+
const handleDone = onDone ?? (() => Keyboard.dismiss());
|
|
64
|
+
const handlePrev = onPrev ?? form?.focusPrev;
|
|
65
|
+
const handleNext = onNext ?? form?.focusNext;
|
|
66
|
+
const navEnabled = showNavigation ?? Boolean(handlePrev || handleNext);
|
|
67
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
68
|
+
testID: testID,
|
|
69
|
+
accessibilityRole: "toolbar",
|
|
70
|
+
style: [styles.bar, {
|
|
71
|
+
bottom: keyboardHeight,
|
|
72
|
+
backgroundColor: theme.colors.background.elevated,
|
|
73
|
+
borderTopColor: theme.colors.border.primary,
|
|
74
|
+
paddingHorizontal: theme.spacing.md
|
|
75
|
+
}, style],
|
|
76
|
+
children: [navEnabled ? /*#__PURE__*/_jsxs(View, {
|
|
77
|
+
style: styles.nav,
|
|
78
|
+
children: [/*#__PURE__*/_jsx(NavButton, {
|
|
79
|
+
label: "\u2039",
|
|
80
|
+
accessibilityLabel: "Previous field",
|
|
81
|
+
onPress: handlePrev,
|
|
82
|
+
theme: theme
|
|
83
|
+
}), /*#__PURE__*/_jsx(NavButton, {
|
|
84
|
+
label: "\u203A",
|
|
85
|
+
accessibilityLabel: "Next field",
|
|
86
|
+
onPress: handleNext,
|
|
87
|
+
theme: theme
|
|
88
|
+
})]
|
|
89
|
+
}) : /*#__PURE__*/_jsx(View, {}), leading, /*#__PURE__*/_jsx(View, {
|
|
90
|
+
style: styles.spacer
|
|
91
|
+
}), /*#__PURE__*/_jsx(Pressable, {
|
|
92
|
+
onPress: handleDone,
|
|
93
|
+
accessibilityRole: "button",
|
|
94
|
+
accessibilityLabel: doneLabel,
|
|
95
|
+
hitSlop: 8,
|
|
96
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
97
|
+
variant: "body",
|
|
98
|
+
weight: "semibold",
|
|
99
|
+
color: "link",
|
|
100
|
+
children: doneLabel
|
|
101
|
+
})
|
|
102
|
+
})]
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const styles = StyleSheet.create({
|
|
106
|
+
bar: {
|
|
107
|
+
position: 'absolute',
|
|
108
|
+
left: 0,
|
|
109
|
+
right: 0,
|
|
110
|
+
height: 44,
|
|
111
|
+
flexDirection: 'row',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
borderTopWidth: StyleSheet.hairlineWidth
|
|
114
|
+
},
|
|
115
|
+
nav: {
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
alignItems: 'center'
|
|
118
|
+
},
|
|
119
|
+
spacer: {
|
|
120
|
+
flex: 1
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
export { KeyboardToolbar };
|
|
124
|
+
export default KeyboardToolbar;
|
|
125
|
+
//# sourceMappingURL=KeyboardToolbar.js.map
|
|
@@ -39,6 +39,7 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
39
39
|
const wasVisibleRef = useRef(false);
|
|
40
40
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
41
41
|
useEffect(() => {
|
|
42
|
+
const running = [];
|
|
42
43
|
if (visible) {
|
|
43
44
|
// Two Fabric quirks force this combination: backdrop View needs
|
|
44
45
|
// `collapsable={false}` (see render) so flattening doesn't drop it,
|
|
@@ -46,21 +47,25 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
46
47
|
// Animated.View that also sets backgroundColor inline renders as 0 on
|
|
47
48
|
// RN 0.85 / Fabric / iOS, leaving the dim layer invisible. Single
|
|
48
49
|
// 240ms tween — JS thread cost is negligible.
|
|
49
|
-
Animated.timing(backdropAnim, {
|
|
50
|
+
const backdrop = Animated.timing(backdropAnim, {
|
|
50
51
|
toValue: 1,
|
|
51
52
|
duration,
|
|
52
53
|
useNativeDriver: false
|
|
53
|
-
})
|
|
54
|
+
});
|
|
55
|
+
backdrop.start();
|
|
56
|
+
running.push(backdrop);
|
|
54
57
|
if (presentation === 'bottom') {
|
|
55
|
-
Animated.spring(translateYAnim, {
|
|
58
|
+
const slide = Animated.spring(translateYAnim, {
|
|
56
59
|
toValue: 0,
|
|
57
60
|
damping: theme.motion.spring.gentle.damping,
|
|
58
61
|
stiffness: theme.motion.spring.gentle.stiffness,
|
|
59
62
|
mass: theme.motion.spring.gentle.mass,
|
|
60
63
|
useNativeDriver: true
|
|
61
|
-
})
|
|
64
|
+
});
|
|
65
|
+
slide.start();
|
|
66
|
+
running.push(slide);
|
|
62
67
|
} else {
|
|
63
|
-
Animated.parallel([Animated.spring(scaleAnim, {
|
|
68
|
+
const enter = Animated.parallel([Animated.spring(scaleAnim, {
|
|
64
69
|
toValue: 1,
|
|
65
70
|
damping: theme.motion.spring.gentle.damping,
|
|
66
71
|
stiffness: theme.motion.spring.gentle.stiffness,
|
|
@@ -70,7 +75,9 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
70
75
|
toValue: 1,
|
|
71
76
|
duration,
|
|
72
77
|
useNativeDriver: true
|
|
73
|
-
})])
|
|
78
|
+
})]);
|
|
79
|
+
enter.start();
|
|
80
|
+
running.push(enter);
|
|
74
81
|
}
|
|
75
82
|
} else {
|
|
76
83
|
// backdropAnim is JS-driven (see note above) — reset directly so a
|
|
@@ -80,6 +87,10 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
80
87
|
setNativeValue(opacityAnim, 0);
|
|
81
88
|
setNativeValue(translateYAnim, screenHeight);
|
|
82
89
|
}
|
|
90
|
+
// Stop in-flight tweens on unmount / visibility flip so an enter animation
|
|
91
|
+
// never resolves against a torn-down tree (fixes the jest async leak and
|
|
92
|
+
// the equivalent post-unmount work in the real app).
|
|
93
|
+
return () => running.forEach(animation => animation.stop());
|
|
83
94
|
}, [visible, presentation, duration, backdropAnim, scaleAnim, opacityAnim, translateYAnim, screenHeight, theme.motion.spring.gentle.damping, theme.motion.spring.gentle.stiffness, theme.motion.spring.gentle.mass, scaleStartValue]);
|
|
84
95
|
|
|
85
96
|
// Accessibility focus trap: when the modal opens, push screen-reader focus
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
5
|
+
import { useControllableState } from "../../hooks/index.js";
|
|
5
6
|
import { useTheme } from "../../theme/index.js";
|
|
6
7
|
import { triggerHaptic } from "../../utils/index.js";
|
|
7
8
|
import { FieldBase, resolveFieldSize, resolveFieldTextStyle } from "../FieldBase/FieldBase.js";
|
|
@@ -43,6 +44,7 @@ const sanitizeTyped = (raw, allowDecimal) => {
|
|
|
43
44
|
const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
44
45
|
const {
|
|
45
46
|
value,
|
|
47
|
+
defaultValue,
|
|
46
48
|
onChange,
|
|
47
49
|
min = Number.NEGATIVE_INFINITY,
|
|
48
50
|
max = Number.POSITIVE_INFINITY,
|
|
@@ -66,6 +68,11 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
66
68
|
textInputProps,
|
|
67
69
|
testID
|
|
68
70
|
} = props;
|
|
71
|
+
const [current, setCurrent] = useControllableState({
|
|
72
|
+
value,
|
|
73
|
+
defaultValue: defaultValue ?? props.min ?? 0,
|
|
74
|
+
onChange
|
|
75
|
+
});
|
|
69
76
|
const theme = useTheme();
|
|
70
77
|
const sizeTokens = resolveFieldSize(theme, size);
|
|
71
78
|
const fieldText = resolveFieldTextStyle(theme, {
|
|
@@ -76,7 +83,7 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
76
83
|
const longPressInterval = theme.components.numberInput?.longPressIntervalMs ?? DEFAULT_LONG_PRESS_INTERVAL;
|
|
77
84
|
const pressHaptic = theme.components.numberInput?.pressHaptic ?? false;
|
|
78
85
|
const inputRef = useRef(null);
|
|
79
|
-
const [draft, setDraft] = useState(formatValue(
|
|
86
|
+
const [draft, setDraft] = useState(formatValue(current, allowDecimal, precision));
|
|
80
87
|
const [editing, setEditing] = useState(false);
|
|
81
88
|
const [focused, setFocused] = useState(false);
|
|
82
89
|
const repeatTimeoutRef = useRef(null);
|
|
@@ -94,41 +101,41 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
94
101
|
useEffect(() => clearRepeat, [clearRepeat]);
|
|
95
102
|
useEffect(() => {
|
|
96
103
|
if (!editing) {
|
|
97
|
-
setDraft(formatValue(
|
|
104
|
+
setDraft(formatValue(current, allowDecimal, precision));
|
|
98
105
|
}
|
|
99
|
-
}, [
|
|
100
|
-
const atMin =
|
|
101
|
-
const atMax =
|
|
106
|
+
}, [current, allowDecimal, precision, editing]);
|
|
107
|
+
const atMin = current <= min;
|
|
108
|
+
const atMax = current >= max;
|
|
102
109
|
const interactive = !disabled;
|
|
103
110
|
const setExternal = useCallback(next => {
|
|
104
111
|
const rounded = roundToPrecision(next, allowDecimal ? precision : 0);
|
|
105
112
|
const clamped = clamp(rounded, min, max);
|
|
106
|
-
if (clamped !==
|
|
107
|
-
|
|
113
|
+
if (clamped !== current) {
|
|
114
|
+
setCurrent(clamped);
|
|
108
115
|
}
|
|
109
|
-
}, [allowDecimal, max, min,
|
|
116
|
+
}, [allowDecimal, max, min, setCurrent, precision, current]);
|
|
110
117
|
const decrement = useCallback(() => {
|
|
111
118
|
if (!interactive || atMin) return;
|
|
112
119
|
if (pressHaptic) triggerHaptic('impactLight');
|
|
113
|
-
setExternal(
|
|
114
|
-
}, [atMin, interactive, pressHaptic, setExternal, step,
|
|
120
|
+
setExternal(current - step);
|
|
121
|
+
}, [atMin, interactive, pressHaptic, setExternal, step, current]);
|
|
115
122
|
const increment = useCallback(() => {
|
|
116
123
|
if (!interactive || atMax) return;
|
|
117
124
|
if (pressHaptic) triggerHaptic('impactLight');
|
|
118
|
-
setExternal(
|
|
119
|
-
}, [atMax, interactive, pressHaptic, setExternal, step,
|
|
125
|
+
setExternal(current + step);
|
|
126
|
+
}, [atMax, interactive, pressHaptic, setExternal, step, current]);
|
|
120
127
|
React.useImperativeHandle(ref, () => ({
|
|
121
128
|
focus: () => inputRef.current?.focus(),
|
|
122
129
|
blur: () => inputRef.current?.blur(),
|
|
123
130
|
clear: () => {
|
|
124
131
|
setDraft('');
|
|
125
132
|
if (Number.isFinite(min) && min > 0) {
|
|
126
|
-
|
|
133
|
+
setCurrent(min);
|
|
127
134
|
} else {
|
|
128
|
-
|
|
135
|
+
setCurrent(0 < min ? min : 0 > max ? max : 0);
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
|
-
}), [max, min,
|
|
138
|
+
}), [max, min, setCurrent]);
|
|
132
139
|
const startRepeating = useCallback(direction => {
|
|
133
140
|
if (!interactive) return;
|
|
134
141
|
clearRepeat();
|
|
@@ -148,27 +155,27 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
148
155
|
const parsed = Number(cleaned);
|
|
149
156
|
if (cleaned !== '' && cleaned !== '-' && cleaned !== '.' && Number.isFinite(parsed)) {
|
|
150
157
|
if (parsed >= min && parsed <= max) {
|
|
151
|
-
if (parsed !==
|
|
152
|
-
|
|
158
|
+
if (parsed !== current) {
|
|
159
|
+
setCurrent(roundToPrecision(parsed, allowDecimal ? precision : 0));
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
|
-
}, [allowDecimal, max, min,
|
|
163
|
+
}, [allowDecimal, max, min, setCurrent, precision, current]);
|
|
157
164
|
const handleBlur = useCallback(() => {
|
|
158
165
|
setEditing(false);
|
|
159
166
|
setFocused(false);
|
|
160
167
|
const parsed = Number(draft);
|
|
161
168
|
if (draft === '' || !Number.isFinite(parsed)) {
|
|
162
|
-
setDraft(formatValue(
|
|
169
|
+
setDraft(formatValue(current, allowDecimal, precision));
|
|
163
170
|
return;
|
|
164
171
|
}
|
|
165
172
|
const rounded = roundToPrecision(parsed, allowDecimal ? precision : 0);
|
|
166
173
|
const clamped = clamp(rounded, min, max);
|
|
167
|
-
if (clamped !==
|
|
168
|
-
|
|
174
|
+
if (clamped !== current) {
|
|
175
|
+
setCurrent(clamped);
|
|
169
176
|
}
|
|
170
177
|
setDraft(formatValue(clamped, allowDecimal, precision));
|
|
171
|
-
}, [allowDecimal, draft, max, min,
|
|
178
|
+
}, [allowDecimal, draft, max, min, setCurrent, precision, current]);
|
|
172
179
|
const handleFocus = useCallback(() => {
|
|
173
180
|
setEditing(true);
|
|
174
181
|
setFocused(true);
|
|
@@ -232,7 +239,7 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
232
239
|
accessibilityRole: "adjustable",
|
|
233
240
|
accessibilityLabel: accessibleInputLabel,
|
|
234
241
|
accessibilityValue: {
|
|
235
|
-
now: Number.isFinite(
|
|
242
|
+
now: Number.isFinite(current) ? current : 0
|
|
236
243
|
},
|
|
237
244
|
accessibilityState: {
|
|
238
245
|
disabled
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Animated, Easing, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
5
5
|
import { fontFor, useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
|
|
6
|
+
import { useReducedMotion, useControllableState } from "../../hooks/index.js";
|
|
6
7
|
import { triggerHaptic } from "../../utils/index.js";
|
|
7
8
|
import { FieldBase } from "../FieldBase/FieldBase.js";
|
|
8
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -36,6 +37,7 @@ const sanitizeChar = (input, kind) => {
|
|
|
36
37
|
const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
37
38
|
const {
|
|
38
39
|
value,
|
|
40
|
+
defaultValue,
|
|
39
41
|
onChange,
|
|
40
42
|
onComplete,
|
|
41
43
|
length = 6,
|
|
@@ -51,6 +53,11 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
51
53
|
textStyle,
|
|
52
54
|
testID
|
|
53
55
|
} = props;
|
|
56
|
+
const [current, setCurrent] = useControllableState({
|
|
57
|
+
value,
|
|
58
|
+
defaultValue: defaultValue ?? '',
|
|
59
|
+
onChange
|
|
60
|
+
});
|
|
54
61
|
const theme = useTheme();
|
|
55
62
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
56
63
|
const sizeStyles = {
|
|
@@ -81,6 +88,7 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
81
88
|
}, [length]);
|
|
82
89
|
const previousFocusedIndexRef = useRef(-1);
|
|
83
90
|
const shakeOnError = theme.components.otpInput?.shakeOnError ?? false;
|
|
91
|
+
const reduceMotion = useReducedMotion();
|
|
84
92
|
const errorHaptic = theme.components.otpInput?.errorHaptic ?? false;
|
|
85
93
|
const selectionHaptic = theme.components.otpInput?.selectionHaptic ?? false;
|
|
86
94
|
const hasError = Boolean(error);
|
|
@@ -90,11 +98,12 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
90
98
|
// error is initially true — there's no transition to animate.
|
|
91
99
|
useEffect(() => {
|
|
92
100
|
const isFirstRun = previousErrorRef.current === null;
|
|
101
|
+
let anim;
|
|
93
102
|
if (!isFirstRun && hasError && !previousErrorRef.current) {
|
|
94
103
|
if (errorHaptic) triggerHaptic('notificationError');
|
|
95
|
-
if (shakeOnError) {
|
|
104
|
+
if (shakeOnError && !reduceMotion) {
|
|
96
105
|
setNativeValue(shake, 0);
|
|
97
|
-
Animated.sequence([Animated.timing(shake, {
|
|
106
|
+
anim = Animated.sequence([Animated.timing(shake, {
|
|
98
107
|
toValue: 1,
|
|
99
108
|
duration: 75,
|
|
100
109
|
easing: Easing.linear,
|
|
@@ -114,10 +123,12 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
114
123
|
duration: 75,
|
|
115
124
|
easing: Easing.linear,
|
|
116
125
|
useNativeDriver: true
|
|
117
|
-
})])
|
|
126
|
+
})]);
|
|
127
|
+
anim.start();
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
previousErrorRef.current = hasError;
|
|
131
|
+
return () => anim?.stop();
|
|
121
132
|
}, [hasError, shake, shakeOnError, errorHaptic]);
|
|
122
133
|
|
|
123
134
|
// Animate underline opacity for the focused cell. Skip on first mount
|
|
@@ -127,17 +138,21 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
127
138
|
previousFocusedIndexRef.current = focusedIndex;
|
|
128
139
|
if (previous === focusedIndex) return;
|
|
129
140
|
if (previous === -1 && focusedIndex === -1) return;
|
|
141
|
+
const anims = [];
|
|
130
142
|
underlines.forEach((anim, idx) => {
|
|
131
143
|
const target = idx === focusedIndex ? 1 : 0;
|
|
132
144
|
// Only animate the cells whose target changed.
|
|
133
145
|
if (idx !== focusedIndex && idx !== previous) return;
|
|
134
|
-
Animated.timing(anim, {
|
|
146
|
+
const animation = Animated.timing(anim, {
|
|
135
147
|
toValue: target,
|
|
136
148
|
duration: theme.motion.duration.fast,
|
|
137
149
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
138
150
|
useNativeDriver: true
|
|
139
|
-
})
|
|
151
|
+
});
|
|
152
|
+
animation.start();
|
|
153
|
+
anims.push(animation);
|
|
140
154
|
});
|
|
155
|
+
return () => anims.forEach(a => a.stop());
|
|
141
156
|
}, [focusedIndex, underlines, theme]);
|
|
142
157
|
const focusCell = useCallback(index => {
|
|
143
158
|
const target = inputsRef.current[index];
|
|
@@ -152,10 +167,10 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
152
167
|
current?.blur();
|
|
153
168
|
},
|
|
154
169
|
clear: () => {
|
|
155
|
-
|
|
170
|
+
setCurrent('');
|
|
156
171
|
focusCell(0);
|
|
157
172
|
}
|
|
158
|
-
}), [focusCell, focusedIndex,
|
|
173
|
+
}), [focusCell, focusedIndex, setCurrent]);
|
|
159
174
|
useEffect(() => {
|
|
160
175
|
if (autoFocus && !disabled) {
|
|
161
176
|
// Defer one frame so the inputs are mounted.
|
|
@@ -166,19 +181,19 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
166
181
|
}, [autoFocus, disabled, focusCell]);
|
|
167
182
|
const cells = useMemo(() => {
|
|
168
183
|
const arr = new Array(length).fill('');
|
|
169
|
-
for (let i = 0; i < Math.min(
|
|
170
|
-
arr[i] =
|
|
184
|
+
for (let i = 0; i < Math.min(current.length, length); i += 1) {
|
|
185
|
+
arr[i] = current[i] ?? '';
|
|
171
186
|
}
|
|
172
187
|
return arr;
|
|
173
|
-
}, [
|
|
188
|
+
}, [current, length]);
|
|
174
189
|
const updateValue = useCallback(next => {
|
|
175
190
|
const trimmed = next.slice(0, length);
|
|
176
|
-
if (trimmed ===
|
|
177
|
-
|
|
191
|
+
if (trimmed === current) return;
|
|
192
|
+
setCurrent(trimmed);
|
|
178
193
|
if (trimmed.length === length && onComplete) {
|
|
179
194
|
onComplete(trimmed);
|
|
180
195
|
}
|
|
181
|
-
}, [length,
|
|
196
|
+
}, [length, setCurrent, onComplete, current]);
|
|
182
197
|
const handleChangeText = useCallback((index, raw) => {
|
|
183
198
|
// Strip the ZWSP placeholder (used so iOS fires onKeyPress/Backspace on otherwise-empty cells).
|
|
184
199
|
const stripped = raw.replace(/\u200B/g, '');
|
|
@@ -208,7 +223,7 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
208
223
|
writeIndex += 1;
|
|
209
224
|
}
|
|
210
225
|
const next = chars.join('').slice(0, length);
|
|
211
|
-
const previousLength =
|
|
226
|
+
const previousLength = current.length;
|
|
212
227
|
updateValue(next);
|
|
213
228
|
if (next.length !== previousLength && selectionHaptic) {
|
|
214
229
|
triggerHaptic('selection');
|
|
@@ -221,7 +236,7 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
221
236
|
} else {
|
|
222
237
|
focusCell(nextFocus);
|
|
223
238
|
}
|
|
224
|
-
}, [cells, focusCell, keyboardType, length, selectionHaptic, updateValue,
|
|
239
|
+
}, [cells, focusCell, keyboardType, length, selectionHaptic, updateValue, current]);
|
|
225
240
|
const handleKeyPress = useCallback((index, e) => {
|
|
226
241
|
const key = e.nativeEvent.key;
|
|
227
242
|
if (key !== 'Backspace') return;
|
|
@@ -38,7 +38,7 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
38
38
|
const {
|
|
39
39
|
value,
|
|
40
40
|
selected: selectedProp,
|
|
41
|
-
|
|
41
|
+
onChange,
|
|
42
42
|
disabled: disabledProp = false,
|
|
43
43
|
label,
|
|
44
44
|
size = 'md',
|
|
@@ -68,13 +68,15 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
68
68
|
const resolvedHaptic = haptic === undefined ? pressHapticEnabled ? 'selection' : false : haptic;
|
|
69
69
|
const progress = useRef(createAnimatedValue(selected ? 1 : 0)).current;
|
|
70
70
|
useEffect(() => {
|
|
71
|
-
Animated.spring(progress, {
|
|
71
|
+
const anim = Animated.spring(progress, {
|
|
72
72
|
toValue: selected ? 1 : 0,
|
|
73
73
|
damping: theme.motion.spring.snappy.damping,
|
|
74
74
|
stiffness: theme.motion.spring.snappy.stiffness,
|
|
75
75
|
mass: theme.motion.spring.snappy.mass,
|
|
76
76
|
useNativeDriver: true
|
|
77
|
-
})
|
|
77
|
+
});
|
|
78
|
+
anim.start();
|
|
79
|
+
return () => anim.stop();
|
|
78
80
|
}, [selected, progress, theme.motion.spring.snappy]);
|
|
79
81
|
const scale = progress.interpolate({
|
|
80
82
|
inputRange: [0, 1],
|
|
@@ -84,9 +86,9 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
84
86
|
if (disabled) return;
|
|
85
87
|
if (resolvedHaptic !== false) triggerHaptic(resolvedHaptic);
|
|
86
88
|
if (ctx) {
|
|
87
|
-
ctx.
|
|
89
|
+
ctx.onChange(value);
|
|
88
90
|
} else {
|
|
89
|
-
|
|
91
|
+
onChange?.(value);
|
|
90
92
|
}
|
|
91
93
|
rest.onPressOut?.(event);
|
|
92
94
|
};
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import React, { useMemo } from 'react';
|
|
4
4
|
import { View } from 'react-native';
|
|
5
5
|
import { RadioGroupContext } from "./Radio.js";
|
|
6
|
+
import { useControllableState } from "../../hooks/index.js";
|
|
6
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
8
|
const RadioGroup = ({
|
|
8
9
|
value,
|
|
10
|
+
defaultValue,
|
|
9
11
|
onChange,
|
|
10
12
|
children,
|
|
11
13
|
disabled = false,
|
|
@@ -13,11 +15,16 @@ const RadioGroup = ({
|
|
|
13
15
|
style,
|
|
14
16
|
testID
|
|
15
17
|
}) => {
|
|
18
|
+
const [current, setCurrent] = useControllableState({
|
|
19
|
+
value,
|
|
20
|
+
defaultValue: defaultValue ?? undefined,
|
|
21
|
+
onChange: onChange
|
|
22
|
+
});
|
|
16
23
|
const ctx = useMemo(() => ({
|
|
17
|
-
selectedValue:
|
|
18
|
-
|
|
24
|
+
selectedValue: current,
|
|
25
|
+
onChange: setCurrent,
|
|
19
26
|
disabled
|
|
20
|
-
}), [
|
|
27
|
+
}), [current, setCurrent, disabled]);
|
|
21
28
|
return /*#__PURE__*/_jsx(RadioGroupContext.Provider, {
|
|
22
29
|
value: ctx,
|
|
23
30
|
children: /*#__PURE__*/_jsx(View, {
|
|
@@ -75,12 +75,14 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
75
75
|
}, [debouncedValue, debounceMs, onChangeText]);
|
|
76
76
|
const showCancelButton = showCancel && isFocused;
|
|
77
77
|
useEffect(() => {
|
|
78
|
-
Animated.timing(cancelAnim, {
|
|
78
|
+
const anim = Animated.timing(cancelAnim, {
|
|
79
79
|
toValue: showCancelButton ? 1 : 0,
|
|
80
80
|
duration: 200,
|
|
81
81
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
82
82
|
useNativeDriver: false
|
|
83
|
-
})
|
|
83
|
+
});
|
|
84
|
+
anim.start();
|
|
85
|
+
return () => anim.stop();
|
|
84
86
|
}, [showCancelButton, cancelAnim, theme.motion.easing.standard]);
|
|
85
87
|
const handleChangeText = useCallback(text => {
|
|
86
88
|
if (debounceMs !== undefined) {
|
|
@@ -12,6 +12,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState }
|
|
|
12
12
|
import { Animated, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
13
13
|
import { useTheme, createAnimatedValue } from "../../theme/index.js";
|
|
14
14
|
import { triggerHaptic } from "../../utils/index.js";
|
|
15
|
+
import { useControllableState } from "../../hooks/index.js";
|
|
15
16
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
17
|
const sizeMap = {
|
|
17
18
|
sm: {
|
|
@@ -29,7 +30,8 @@ const TRACK_PADDING = 2;
|
|
|
29
30
|
const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
|
|
30
31
|
const {
|
|
31
32
|
segments,
|
|
32
|
-
value,
|
|
33
|
+
value: valueProp,
|
|
34
|
+
defaultValue,
|
|
33
35
|
onChange,
|
|
34
36
|
size = 'md',
|
|
35
37
|
fullWidth = true,
|
|
@@ -44,6 +46,11 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
44
46
|
labelStyle,
|
|
45
47
|
testID
|
|
46
48
|
} = props;
|
|
49
|
+
const [current, setCurrent] = useControllableState({
|
|
50
|
+
value: valueProp,
|
|
51
|
+
defaultValue: defaultValue ?? segments[0]?.value,
|
|
52
|
+
onChange
|
|
53
|
+
});
|
|
47
54
|
const theme = useTheme();
|
|
48
55
|
const segTheme = theme.components.segmentedControl;
|
|
49
56
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
@@ -64,23 +71,26 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
64
71
|
// only animate translateX via the native driver.
|
|
65
72
|
const [trackWidth, setTrackWidth] = useState(0);
|
|
66
73
|
const thumbTranslateX = useRef(createAnimatedValue(0)).current;
|
|
67
|
-
const activeIndex = Math.max(0, segments.findIndex(s => s.value ===
|
|
74
|
+
const activeIndex = Math.max(0, segments.findIndex(s => s.value === current));
|
|
68
75
|
const segmentWidth = trackWidth > 0 ? (trackWidth - trackPadding * 2) / Math.max(segments.length, 1) : 0;
|
|
69
76
|
const animateThumb = useCallback((index, segWidth) => {
|
|
70
|
-
if (segWidth <= 0) return;
|
|
77
|
+
if (segWidth <= 0) return undefined;
|
|
71
78
|
const targetX = trackPadding + segWidth * index;
|
|
72
79
|
const spring = theme.motion.spring.snappy;
|
|
73
|
-
Animated.spring(thumbTranslateX, {
|
|
80
|
+
const anim = Animated.spring(thumbTranslateX, {
|
|
74
81
|
toValue: targetX,
|
|
75
82
|
damping: spring.damping,
|
|
76
83
|
stiffness: spring.stiffness,
|
|
77
84
|
mass: spring.mass,
|
|
78
85
|
useNativeDriver: true
|
|
79
|
-
})
|
|
86
|
+
});
|
|
87
|
+
anim.start();
|
|
88
|
+
return anim;
|
|
80
89
|
}, [theme.motion.spring.snappy, thumbTranslateX]);
|
|
81
90
|
useEffect(() => {
|
|
82
91
|
if (segmentWidth > 0) {
|
|
83
|
-
animateThumb(activeIndex, segmentWidth);
|
|
92
|
+
const anim = animateThumb(activeIndex, segmentWidth);
|
|
93
|
+
return () => anim?.stop();
|
|
84
94
|
}
|
|
85
95
|
}, [activeIndex, segmentWidth, animateThumb]);
|
|
86
96
|
const handleTrackLayout = useCallback(e => {
|
|
@@ -91,10 +101,10 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
91
101
|
}, [trackWidth]);
|
|
92
102
|
const handlePress = useCallback(segment => {
|
|
93
103
|
if (disabled) return;
|
|
94
|
-
if (segment.value ===
|
|
104
|
+
if (segment.value === current) return;
|
|
95
105
|
if (changeHapticEnabled) triggerHaptic('selection');
|
|
96
|
-
|
|
97
|
-
}, [disabled,
|
|
106
|
+
setCurrent(segment.value);
|
|
107
|
+
}, [disabled, setCurrent, current, changeHapticEnabled]);
|
|
98
108
|
const thumbBg = tone === 'primary' ? theme.colors.background.elevated : theme.colors.background.elevated;
|
|
99
109
|
const activeTextColor = tone === 'primary' ? theme.colors.text.primary : theme.colors.text.primary;
|
|
100
110
|
return /*#__PURE__*/_jsxs(View, {
|
|
@@ -125,7 +135,7 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
125
135
|
...theme.shadows.sm
|
|
126
136
|
}, selectedIndicatorStyle]
|
|
127
137
|
}) : null, segments.map(segment => {
|
|
128
|
-
const isActive = segment.value ===
|
|
138
|
+
const isActive = segment.value === current;
|
|
129
139
|
return /*#__PURE__*/_jsxs(Pressable, {
|
|
130
140
|
onPress: () => handlePress(segment),
|
|
131
141
|
disabled: disabled,
|