@webority-technologies/mobile 0.0.22 → 0.0.24
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 +9 -7
- package/lib/commonjs/components/AnimatePresence/AnimatePresence.js +69 -0
- package/lib/commonjs/components/AnimatePresence/index.js +13 -0
- package/lib/commonjs/components/AppBar/AppBar.js +9 -6
- 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 +20 -6
- 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/Card/Card.js +3 -3
- package/lib/commonjs/components/Carousel/Carousel.js +4 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +17 -7
- package/lib/commonjs/components/Chip/Chip.js +4 -2
- package/lib/commonjs/components/DatePicker/DatePicker.js +31 -24
- package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +16 -11
- package/lib/commonjs/components/Dialog/Dialog.js +6 -4
- package/lib/commonjs/components/Drawer/Drawer.js +4 -2
- package/lib/commonjs/components/FieldBase/FieldBase.js +8 -4
- package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +23 -13
- package/lib/commonjs/components/FormField/FormField.js +61 -25
- package/lib/commonjs/components/ImageGallery/ImageGallery.js +17 -15
- 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/ListItem/ListItem.js +4 -3
- package/lib/commonjs/components/Modal/Modal.js +21 -9
- package/lib/commonjs/components/NumberInput/NumberInput.js +38 -29
- package/lib/commonjs/components/OTPInput/OTPInput.js +37 -22
- package/lib/commonjs/components/Radio/Radio.js +9 -8
- package/lib/commonjs/components/Radio/RadioGroup.js +10 -3
- package/lib/commonjs/components/Rating/Rating.js +4 -3
- package/lib/commonjs/components/SearchBar/SearchBar.js +11 -6
- package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +23 -12
- package/lib/commonjs/components/Select/Select.js +40 -36
- package/lib/commonjs/components/Skeleton/SkeletonContent.js +5 -2
- package/lib/commonjs/components/Slider/Slider.js +241 -225
- package/lib/commonjs/components/Spinner/Spinner.js +5 -5
- package/lib/commonjs/components/Stepper/Stepper.js +6 -5
- package/lib/commonjs/components/Swipeable/Swipeable.js +8 -9
- package/lib/commonjs/components/Switch/Switch.js +29 -16
- package/lib/commonjs/components/Tabs/Tabs.js +8 -5
- 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 +23 -15
- package/lib/commonjs/components/Toast/Toast.js +22 -10
- package/lib/commonjs/components/Tooltip/Tooltip.js +6 -2
- package/lib/commonjs/components/index.js +156 -103
- 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/commonjs/utils/hapticUtils.js +11 -1
- package/lib/commonjs/utils/index.js +6 -0
- package/lib/module/components/Accordion/Accordion.js +10 -8
- package/lib/module/components/AnimatePresence/AnimatePresence.js +63 -0
- package/lib/module/components/AnimatePresence/index.js +4 -0
- package/lib/module/components/AppBar/AppBar.js +10 -7
- 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 +20 -6
- 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/Card/Card.js +4 -4
- package/lib/module/components/Carousel/Carousel.js +4 -2
- package/lib/module/components/Checkbox/Checkbox.js +18 -8
- package/lib/module/components/Chip/Chip.js +5 -3
- package/lib/module/components/DatePicker/DatePicker.js +32 -25
- package/lib/module/components/DateRangePicker/DateRangePicker.js +17 -12
- package/lib/module/components/Dialog/Dialog.js +7 -5
- package/lib/module/components/Drawer/Drawer.js +5 -3
- package/lib/module/components/FieldBase/FieldBase.js +8 -4
- package/lib/module/components/FloatingActionButton/FloatingActionButton.js +24 -14
- package/lib/module/components/FormField/FormField.js +62 -26
- package/lib/module/components/ImageGallery/ImageGallery.js +18 -16
- 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/ListItem/ListItem.js +5 -4
- package/lib/module/components/Modal/Modal.js +22 -10
- package/lib/module/components/NumberInput/NumberInput.js +36 -27
- package/lib/module/components/OTPInput/OTPInput.js +37 -22
- package/lib/module/components/Radio/Radio.js +10 -9
- package/lib/module/components/Radio/RadioGroup.js +10 -3
- package/lib/module/components/Rating/Rating.js +5 -4
- package/lib/module/components/SearchBar/SearchBar.js +12 -7
- package/lib/module/components/SegmentedControl/SegmentedControl.js +24 -13
- package/lib/module/components/Select/Select.js +41 -37
- package/lib/module/components/Skeleton/SkeletonContent.js +5 -2
- package/lib/module/components/Slider/Slider.js +244 -228
- package/lib/module/components/Spinner/Spinner.js +5 -5
- package/lib/module/components/Stepper/Stepper.js +7 -6
- package/lib/module/components/Swipeable/Swipeable.js +9 -10
- package/lib/module/components/Switch/Switch.js +29 -16
- package/lib/module/components/Tabs/Tabs.js +9 -6
- 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 +24 -16
- package/lib/module/components/Toast/Toast.js +22 -10
- package/lib/module/components/Tooltip/Tooltip.js +6 -2
- package/lib/module/components/index.js +5 -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/module/utils/hapticUtils.js +9 -0
- package/lib/module/utils/index.js +1 -1
- package/lib/typescript/commonjs/components/Accordion/Accordion.d.ts +3 -0
- package/lib/typescript/commonjs/components/AnimatePresence/AnimatePresence.d.ts +30 -0
- package/lib/typescript/commonjs/components/AnimatePresence/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/AppBar/AppBar.d.ts +6 -0
- package/lib/typescript/commonjs/components/Banner/Banner.d.ts +3 -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/Card/Card.d.ts +3 -0
- package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +4 -2
- package/lib/typescript/commonjs/components/Chip/Chip.d.ts +3 -0
- package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +6 -3
- package/lib/typescript/commonjs/components/DateRangePicker/DateRangePicker.d.ts +6 -0
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +5 -2
- package/lib/typescript/commonjs/components/Drawer/Drawer.d.ts +3 -0
- package/lib/typescript/commonjs/components/FloatingActionButton/FloatingActionButton.d.ts +5 -0
- package/lib/typescript/commonjs/components/FormField/FormField.d.ts +13 -2
- package/lib/typescript/commonjs/components/ImageGallery/ImageGallery.d.ts +6 -0
- 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/ListItem/ListItem.d.ts +3 -0
- package/lib/typescript/commonjs/components/Modal/Modal.d.ts +6 -0
- package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +6 -2
- package/lib/typescript/commonjs/components/OTPInput/OTPInput.d.ts +9 -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/Rating/Rating.d.ts +6 -0
- package/lib/typescript/commonjs/components/SearchBar/SearchBar.d.ts +3 -0
- package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +6 -2
- package/lib/typescript/commonjs/components/Select/Select.d.ts +6 -0
- package/lib/typescript/commonjs/components/Slider/Slider.d.ts +9 -4
- package/lib/typescript/commonjs/components/Spinner/Spinner.d.ts +1 -1
- package/lib/typescript/commonjs/components/Stepper/Stepper.d.ts +6 -0
- package/lib/typescript/commonjs/components/Swipeable/Swipeable.d.ts +3 -0
- package/lib/typescript/commonjs/components/Switch/Switch.d.ts +3 -2
- package/lib/typescript/commonjs/components/Tabs/Tabs.d.ts +3 -0
- 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 +6 -3
- package/lib/typescript/commonjs/components/index.d.ts +10 -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 +17 -67
- package/lib/typescript/commonjs/utils/hapticUtils.d.ts +8 -0
- package/lib/typescript/commonjs/utils/index.d.ts +1 -1
- package/lib/typescript/module/components/Accordion/Accordion.d.ts +3 -0
- package/lib/typescript/module/components/AnimatePresence/AnimatePresence.d.ts +30 -0
- package/lib/typescript/module/components/AnimatePresence/index.d.ts +3 -0
- package/lib/typescript/module/components/AppBar/AppBar.d.ts +6 -0
- package/lib/typescript/module/components/Banner/Banner.d.ts +3 -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/Card/Card.d.ts +3 -0
- package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +4 -2
- package/lib/typescript/module/components/Chip/Chip.d.ts +3 -0
- package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +6 -3
- package/lib/typescript/module/components/DateRangePicker/DateRangePicker.d.ts +6 -0
- package/lib/typescript/module/components/Dialog/Dialog.d.ts +5 -2
- package/lib/typescript/module/components/Drawer/Drawer.d.ts +3 -0
- package/lib/typescript/module/components/FloatingActionButton/FloatingActionButton.d.ts +5 -0
- package/lib/typescript/module/components/FormField/FormField.d.ts +13 -2
- package/lib/typescript/module/components/ImageGallery/ImageGallery.d.ts +6 -0
- 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/ListItem/ListItem.d.ts +3 -0
- package/lib/typescript/module/components/Modal/Modal.d.ts +6 -0
- package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +6 -2
- package/lib/typescript/module/components/OTPInput/OTPInput.d.ts +9 -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/Rating/Rating.d.ts +6 -0
- package/lib/typescript/module/components/SearchBar/SearchBar.d.ts +3 -0
- package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +6 -2
- package/lib/typescript/module/components/Select/Select.d.ts +6 -0
- package/lib/typescript/module/components/Slider/Slider.d.ts +9 -4
- package/lib/typescript/module/components/Spinner/Spinner.d.ts +1 -1
- package/lib/typescript/module/components/Stepper/Stepper.d.ts +6 -0
- package/lib/typescript/module/components/Swipeable/Swipeable.d.ts +3 -0
- package/lib/typescript/module/components/Switch/Switch.d.ts +3 -2
- package/lib/typescript/module/components/Tabs/Tabs.d.ts +3 -0
- 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 +6 -3
- package/lib/typescript/module/components/index.d.ts +10 -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 +17 -67
- package/lib/typescript/module/utils/hapticUtils.d.ts +8 -0
- package/lib/typescript/module/utils/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState }
|
|
|
4
4
|
import { Animated, Easing, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
5
5
|
import { createAnimatedValue, fontFor, setNativeValue, useTheme } from "../../theme/index.js";
|
|
6
6
|
import { triggerHaptic } from "../../utils/index.js";
|
|
7
|
+
import { useReducedMotion } from "../../hooks/index.js";
|
|
7
8
|
import { FieldBase, resolveFieldSize, resolveFieldTextStyle, resolveVariantColors } from "../FieldBase/FieldBase.js";
|
|
8
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
10
|
// FieldBase carries every dimension except `multilineMinHeight` — Input owns
|
|
@@ -79,26 +80,31 @@ const Input = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
79
80
|
|
|
80
81
|
// Floating label animation
|
|
81
82
|
useEffect(() => {
|
|
82
|
-
Animated.timing(labelAnim, {
|
|
83
|
+
const anim = Animated.timing(labelAnim, {
|
|
83
84
|
toValue: shouldFloat ? 1 : 0,
|
|
84
85
|
duration: theme.motion.duration.fast,
|
|
85
86
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
86
87
|
useNativeDriver: false
|
|
87
|
-
})
|
|
88
|
+
});
|
|
89
|
+
anim.start();
|
|
90
|
+
return () => anim.stop();
|
|
88
91
|
}, [shouldFloat, labelAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
|
|
89
92
|
|
|
90
93
|
// Helper / error fade
|
|
91
94
|
useEffect(() => {
|
|
92
|
-
Animated.timing(messageAnim, {
|
|
95
|
+
const anim = Animated.timing(messageAnim, {
|
|
93
96
|
toValue: hasError || Boolean(helperText) ? 1 : 0,
|
|
94
|
-
duration:
|
|
97
|
+
duration: theme.motion.duration.fast,
|
|
95
98
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
96
99
|
useNativeDriver: true
|
|
97
|
-
})
|
|
98
|
-
|
|
100
|
+
});
|
|
101
|
+
anim.start();
|
|
102
|
+
return () => anim.stop();
|
|
103
|
+
}, [hasError, helperText, messageAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
|
|
99
104
|
|
|
100
105
|
// Error shake — off by default; consumers opt in via
|
|
101
106
|
// theme.components.input.shakeOnError. The haptic ships with the shake.
|
|
107
|
+
const reduceMotion = useReducedMotion();
|
|
102
108
|
const shakeOnError = theme.components.input?.shakeOnError ?? false;
|
|
103
109
|
const prevErrorRef = useRef(hasError);
|
|
104
110
|
useEffect(() => {
|
|
@@ -106,33 +112,39 @@ const Input = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
106
112
|
prevErrorRef.current = hasError;
|
|
107
113
|
return;
|
|
108
114
|
}
|
|
115
|
+
let anim;
|
|
109
116
|
if (hasError && !prevErrorRef.current) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
// Reduce Motion: keep the error haptic, skip the shake.
|
|
118
|
+
if (!reduceMotion) {
|
|
119
|
+
setNativeValue(shakeAnim, 0);
|
|
120
|
+
anim = Animated.sequence([Animated.timing(shakeAnim, {
|
|
121
|
+
toValue: 1,
|
|
122
|
+
duration: 50,
|
|
123
|
+
useNativeDriver: true
|
|
124
|
+
}), Animated.timing(shakeAnim, {
|
|
125
|
+
toValue: -1,
|
|
126
|
+
duration: 50,
|
|
127
|
+
useNativeDriver: true
|
|
128
|
+
}), Animated.timing(shakeAnim, {
|
|
129
|
+
toValue: 1,
|
|
130
|
+
duration: 50,
|
|
131
|
+
useNativeDriver: true
|
|
132
|
+
}), Animated.timing(shakeAnim, {
|
|
133
|
+
toValue: -1,
|
|
134
|
+
duration: 50,
|
|
135
|
+
useNativeDriver: true
|
|
136
|
+
}), Animated.timing(shakeAnim, {
|
|
137
|
+
toValue: 0,
|
|
138
|
+
duration: 100,
|
|
139
|
+
useNativeDriver: true
|
|
140
|
+
})]);
|
|
141
|
+
anim.start();
|
|
142
|
+
}
|
|
132
143
|
triggerHaptic('notificationError');
|
|
133
144
|
}
|
|
134
145
|
prevErrorRef.current = hasError;
|
|
135
|
-
|
|
146
|
+
return () => anim?.stop();
|
|
147
|
+
}, [hasError, shakeAnim, shakeOnError, reduceMotion]);
|
|
136
148
|
const handleFocus = useCallback(e => {
|
|
137
149
|
setIsFocused(true);
|
|
138
150
|
onFocus?.(e);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { findNodeHandle, Keyboard, Platform, ScrollView, TextInput } from 'react-native';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
/**
|
|
7
|
+
* A ScrollView that keeps the focused input visible above the keyboard — no
|
|
8
|
+
* native dependency. On keyboard show it pads the content by the keyboard
|
|
9
|
+
* height and scrolls the focused TextInput so its bottom sits `extraScrollHeight`
|
|
10
|
+
* above the keyboard, but only when it would otherwise be covered (so already-
|
|
11
|
+
* visible fields don't jump). The classic measure-and-scroll approach, in pure RN.
|
|
12
|
+
*/
|
|
13
|
+
const KeyboardAwareScrollView = /*#__PURE__*/forwardRef((props, ref) => {
|
|
14
|
+
const {
|
|
15
|
+
children,
|
|
16
|
+
extraScrollHeight = 24,
|
|
17
|
+
disabled = false,
|
|
18
|
+
contentContainerStyle,
|
|
19
|
+
onScroll,
|
|
20
|
+
onLayout,
|
|
21
|
+
...rest
|
|
22
|
+
} = props;
|
|
23
|
+
const scrollRef = useRef(null);
|
|
24
|
+
const scrollYRef = useRef(0);
|
|
25
|
+
const viewportHeightRef = useRef(0);
|
|
26
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
27
|
+
const setRefs = useCallback(node => {
|
|
28
|
+
scrollRef.current = node;
|
|
29
|
+
if (typeof ref === 'function') ref(node);else if (ref) ref.current = node;
|
|
30
|
+
}, [ref]);
|
|
31
|
+
const scrollFocusedIntoView = useCallback(kbHeight => {
|
|
32
|
+
const scroll = scrollRef.current;
|
|
33
|
+
const scrollNode = scroll ? findNodeHandle(scroll) : null;
|
|
34
|
+
const stateApi = TextInput?.State;
|
|
35
|
+
const focused = stateApi?.currentlyFocusedInput?.();
|
|
36
|
+
if (!scroll || scrollNode == null || !focused?.measureLayout) return;
|
|
37
|
+
focused.measureLayout(scrollNode, (_left, top, _width, height) => {
|
|
38
|
+
const scrollY = scrollYRef.current;
|
|
39
|
+
const viewportH = viewportHeightRef.current;
|
|
40
|
+
if (viewportH <= 0) return;
|
|
41
|
+
const keyboardTop = viewportH - kbHeight - extraScrollHeight;
|
|
42
|
+
const fieldBottomInViewport = top - scrollY + height;
|
|
43
|
+
if (fieldBottomInViewport > keyboardTop) {
|
|
44
|
+
const delta = fieldBottomInViewport - keyboardTop;
|
|
45
|
+
scroll.scrollTo({
|
|
46
|
+
y: scrollY + delta,
|
|
47
|
+
animated: true
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}, () => undefined);
|
|
51
|
+
}, [extraScrollHeight]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (disabled) return undefined;
|
|
54
|
+
// iOS exposes the will* events (smoother); Android only fires did*.
|
|
55
|
+
const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
56
|
+
const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
57
|
+
let rafId;
|
|
58
|
+
const showSub = Keyboard.addListener(showEvent, e => {
|
|
59
|
+
const height = e.endCoordinates?.height ?? 0;
|
|
60
|
+
setKeyboardHeight(height);
|
|
61
|
+
// Wait a frame so the padding is applied before we scroll. Tracked so
|
|
62
|
+
// it can be cancelled on unmount (never runs against a torn-down tree).
|
|
63
|
+
if (rafId != null) cancelAnimationFrame(rafId);
|
|
64
|
+
rafId = requestAnimationFrame(() => scrollFocusedIntoView(height));
|
|
65
|
+
});
|
|
66
|
+
const hideSub = Keyboard.addListener(hideEvent, () => setKeyboardHeight(0));
|
|
67
|
+
return () => {
|
|
68
|
+
if (rafId != null) cancelAnimationFrame(rafId);
|
|
69
|
+
showSub.remove();
|
|
70
|
+
hideSub.remove();
|
|
71
|
+
};
|
|
72
|
+
}, [disabled, scrollFocusedIntoView]);
|
|
73
|
+
const handleScroll = useCallback(e => {
|
|
74
|
+
scrollYRef.current = e.nativeEvent.contentOffset.y;
|
|
75
|
+
onScroll?.(e);
|
|
76
|
+
}, [onScroll]);
|
|
77
|
+
const handleLayout = useCallback(e => {
|
|
78
|
+
viewportHeightRef.current = e.nativeEvent.layout.height;
|
|
79
|
+
onLayout?.(e);
|
|
80
|
+
}, [onLayout]);
|
|
81
|
+
return /*#__PURE__*/_jsx(ScrollView, {
|
|
82
|
+
ref: setRefs,
|
|
83
|
+
onScroll: handleScroll,
|
|
84
|
+
onLayout: handleLayout,
|
|
85
|
+
scrollEventThrottle: 16,
|
|
86
|
+
keyboardShouldPersistTaps: "handled",
|
|
87
|
+
keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'on-drag',
|
|
88
|
+
contentContainerStyle: [contentContainerStyle, {
|
|
89
|
+
paddingBottom: keyboardHeight
|
|
90
|
+
}],
|
|
91
|
+
...rest,
|
|
92
|
+
children: children
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
KeyboardAwareScrollView.displayName = 'KeyboardAwareScrollView';
|
|
96
|
+
export { KeyboardAwareScrollView };
|
|
97
|
+
export default KeyboardAwareScrollView;
|
|
98
|
+
//# sourceMappingURL=KeyboardAwareScrollView.js.map
|
|
@@ -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
|
|
@@ -4,7 +4,7 @@ import React, { forwardRef, useMemo } from 'react';
|
|
|
4
4
|
import { Animated, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { fontFor, useTheme } from "../../theme/index.js";
|
|
6
6
|
import { usePressAnimation } from "../../hooks/usePressAnimation.js";
|
|
7
|
-
import { triggerHaptic } from "../../utils/hapticUtils.js";
|
|
7
|
+
import { resolveHaptic, triggerHaptic } from "../../utils/hapticUtils.js";
|
|
8
8
|
import { Swipeable } from "../Swipeable/index.js";
|
|
9
9
|
import { Skeleton } from "../Skeleton/index.js";
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -64,6 +64,7 @@ const ListItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
64
64
|
left,
|
|
65
65
|
right,
|
|
66
66
|
onPress,
|
|
67
|
+
haptic,
|
|
67
68
|
selected = false,
|
|
68
69
|
disabled = false,
|
|
69
70
|
size = 'md',
|
|
@@ -83,7 +84,6 @@ const ListItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
83
84
|
testID
|
|
84
85
|
} = props;
|
|
85
86
|
const theme = useTheme();
|
|
86
|
-
const pressHaptic = theme.components.listItem?.pressHaptic ?? false;
|
|
87
87
|
const sz = useMemo(() => sizeFor(theme, size), [theme, size]);
|
|
88
88
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
89
89
|
const isInteractive = !!onPress && !disabled;
|
|
@@ -96,7 +96,8 @@ const ListItem = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
96
96
|
});
|
|
97
97
|
const handlePress = event => {
|
|
98
98
|
if (!isInteractive) return;
|
|
99
|
-
|
|
99
|
+
const h = resolveHaptic(haptic, 'selection');
|
|
100
|
+
if (h) triggerHaptic(h);
|
|
100
101
|
onPress?.(event);
|
|
101
102
|
};
|
|
102
103
|
const a11yLabel = accessibilityLabel ?? title;
|
|
@@ -291,7 +292,7 @@ const buildStyles = theme => StyleSheet.create({
|
|
|
291
292
|
backgroundColor: theme.colors.surface.pressed
|
|
292
293
|
},
|
|
293
294
|
disabled: {
|
|
294
|
-
opacity: 0.
|
|
295
|
+
opacity: 0.55
|
|
295
296
|
},
|
|
296
297
|
leftSlot: {
|
|
297
298
|
marginRight: theme.spacing.md,
|
|
@@ -4,7 +4,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'reac
|
|
|
4
4
|
import { AccessibilityInfo, Animated, Dimensions, findNodeHandle, Modal as RNModal, Pressable, StyleSheet, View } from 'react-native';
|
|
5
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
6
|
import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
|
|
7
|
-
import { triggerHaptic } from "../../utils/hapticUtils.js";
|
|
7
|
+
import { triggerHaptic, resolveHaptic } from "../../utils/hapticUtils.js";
|
|
8
8
|
|
|
9
9
|
// Local shape mirror — see types.ts ModalTokens for the canonical definition.
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -22,6 +22,7 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
22
22
|
contentStyle,
|
|
23
23
|
backdropStyle,
|
|
24
24
|
restoreFocusRef,
|
|
25
|
+
haptic,
|
|
25
26
|
testID
|
|
26
27
|
} = props;
|
|
27
28
|
const theme = useTheme();
|
|
@@ -30,7 +31,6 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
30
31
|
const screenHeight = Dimensions.get('window').height;
|
|
31
32
|
const modalTokens = theme.components.modal;
|
|
32
33
|
const scaleStartValue = modalTokens?.scaleStartValue ?? 0.9;
|
|
33
|
-
const backdropHaptic = modalTokens?.backdropHaptic ?? false;
|
|
34
34
|
const backdropAnim = useRef(createAnimatedValue(0)).current;
|
|
35
35
|
const scaleAnim = useRef(createAnimatedValue(scaleStartValue)).current;
|
|
36
36
|
const opacityAnim = useRef(createAnimatedValue(0)).current;
|
|
@@ -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
|
|
@@ -115,9 +126,10 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
115
126
|
}, [visible, duration, restoreFocusRef]);
|
|
116
127
|
const handleBackdropPress = useCallback(() => {
|
|
117
128
|
if (!backdropPressClose) return;
|
|
118
|
-
|
|
129
|
+
const h = resolveHaptic(haptic, 'selection');
|
|
130
|
+
if (h) triggerHaptic(h);
|
|
119
131
|
onRequestClose();
|
|
120
|
-
}, [backdropPressClose, onRequestClose,
|
|
132
|
+
}, [backdropPressClose, onRequestClose, haptic]);
|
|
121
133
|
const handleHardwareBack = useCallback(() => {
|
|
122
134
|
if (hardwareBackPress) {
|
|
123
135
|
onRequestClose();
|
|
@@ -2,8 +2,9 @@
|
|
|
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
|
-
import { triggerHaptic } from "../../utils/index.js";
|
|
7
|
+
import { resolveHaptic, triggerHaptic } from "../../utils/index.js";
|
|
7
8
|
import { FieldBase, resolveFieldSize, resolveFieldTextStyle } from "../FieldBase/FieldBase.js";
|
|
8
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
10
|
const DEFAULT_LONG_PRESS_DELAY = 500;
|
|
@@ -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,
|
|
@@ -56,6 +58,7 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
56
58
|
size = 'md',
|
|
57
59
|
variant = 'inline',
|
|
58
60
|
unit,
|
|
61
|
+
haptic,
|
|
59
62
|
accessibilityLabel,
|
|
60
63
|
style,
|
|
61
64
|
containerStyle,
|
|
@@ -66,6 +69,11 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
66
69
|
textInputProps,
|
|
67
70
|
testID
|
|
68
71
|
} = props;
|
|
72
|
+
const [current, setCurrent] = useControllableState({
|
|
73
|
+
value,
|
|
74
|
+
defaultValue: defaultValue ?? props.min ?? 0,
|
|
75
|
+
onChange
|
|
76
|
+
});
|
|
69
77
|
const theme = useTheme();
|
|
70
78
|
const sizeTokens = resolveFieldSize(theme, size);
|
|
71
79
|
const fieldText = resolveFieldTextStyle(theme, {
|
|
@@ -74,9 +82,8 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
74
82
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
75
83
|
const longPressDelay = theme.components.numberInput?.longPressDelayMs ?? DEFAULT_LONG_PRESS_DELAY;
|
|
76
84
|
const longPressInterval = theme.components.numberInput?.longPressIntervalMs ?? DEFAULT_LONG_PRESS_INTERVAL;
|
|
77
|
-
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,43 @@ 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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
const h = resolveHaptic(haptic, 'selection');
|
|
120
|
+
if (h) triggerHaptic(h);
|
|
121
|
+
setExternal(current - step);
|
|
122
|
+
}, [atMin, interactive, haptic, setExternal, step, current]);
|
|
115
123
|
const increment = useCallback(() => {
|
|
116
124
|
if (!interactive || atMax) return;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
const h = resolveHaptic(haptic, 'selection');
|
|
126
|
+
if (h) triggerHaptic(h);
|
|
127
|
+
setExternal(current + step);
|
|
128
|
+
}, [atMax, interactive, haptic, setExternal, step, current]);
|
|
120
129
|
React.useImperativeHandle(ref, () => ({
|
|
121
130
|
focus: () => inputRef.current?.focus(),
|
|
122
131
|
blur: () => inputRef.current?.blur(),
|
|
123
132
|
clear: () => {
|
|
124
133
|
setDraft('');
|
|
125
134
|
if (Number.isFinite(min) && min > 0) {
|
|
126
|
-
|
|
135
|
+
setCurrent(min);
|
|
127
136
|
} else {
|
|
128
|
-
|
|
137
|
+
setCurrent(0 < min ? min : 0 > max ? max : 0);
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
|
-
}), [max, min,
|
|
140
|
+
}), [max, min, setCurrent]);
|
|
132
141
|
const startRepeating = useCallback(direction => {
|
|
133
142
|
if (!interactive) return;
|
|
134
143
|
clearRepeat();
|
|
@@ -148,27 +157,27 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
148
157
|
const parsed = Number(cleaned);
|
|
149
158
|
if (cleaned !== '' && cleaned !== '-' && cleaned !== '.' && Number.isFinite(parsed)) {
|
|
150
159
|
if (parsed >= min && parsed <= max) {
|
|
151
|
-
if (parsed !==
|
|
152
|
-
|
|
160
|
+
if (parsed !== current) {
|
|
161
|
+
setCurrent(roundToPrecision(parsed, allowDecimal ? precision : 0));
|
|
153
162
|
}
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
|
-
}, [allowDecimal, max, min,
|
|
165
|
+
}, [allowDecimal, max, min, setCurrent, precision, current]);
|
|
157
166
|
const handleBlur = useCallback(() => {
|
|
158
167
|
setEditing(false);
|
|
159
168
|
setFocused(false);
|
|
160
169
|
const parsed = Number(draft);
|
|
161
170
|
if (draft === '' || !Number.isFinite(parsed)) {
|
|
162
|
-
setDraft(formatValue(
|
|
171
|
+
setDraft(formatValue(current, allowDecimal, precision));
|
|
163
172
|
return;
|
|
164
173
|
}
|
|
165
174
|
const rounded = roundToPrecision(parsed, allowDecimal ? precision : 0);
|
|
166
175
|
const clamped = clamp(rounded, min, max);
|
|
167
|
-
if (clamped !==
|
|
168
|
-
|
|
176
|
+
if (clamped !== current) {
|
|
177
|
+
setCurrent(clamped);
|
|
169
178
|
}
|
|
170
179
|
setDraft(formatValue(clamped, allowDecimal, precision));
|
|
171
|
-
}, [allowDecimal, draft, max, min,
|
|
180
|
+
}, [allowDecimal, draft, max, min, setCurrent, precision, current]);
|
|
172
181
|
const handleFocus = useCallback(() => {
|
|
173
182
|
setEditing(true);
|
|
174
183
|
setFocused(true);
|
|
@@ -232,7 +241,7 @@ const NumberInput = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
232
241
|
accessibilityRole: "adjustable",
|
|
233
242
|
accessibilityLabel: accessibleInputLabel,
|
|
234
243
|
accessibilityValue: {
|
|
235
|
-
now: Number.isFinite(
|
|
244
|
+
now: Number.isFinite(current) ? current : 0
|
|
236
245
|
},
|
|
237
246
|
accessibilityState: {
|
|
238
247
|
disabled
|