@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
|
@@ -437,12 +437,14 @@ const ThumbnailItem = ({
|
|
|
437
437
|
}) => {
|
|
438
438
|
const scale = useRef(createAnimatedValue(isActive ? 1.1 : 1)).current;
|
|
439
439
|
useEffect(() => {
|
|
440
|
-
Animated.spring(scale, {
|
|
440
|
+
const anim = Animated.spring(scale, {
|
|
441
441
|
toValue: isActive ? 1.1 : 1,
|
|
442
442
|
useNativeDriver: true,
|
|
443
443
|
friction: 6,
|
|
444
444
|
tension: 80
|
|
445
|
-
})
|
|
445
|
+
});
|
|
446
|
+
anim.start();
|
|
447
|
+
return () => anim.stop();
|
|
446
448
|
}, [isActive, scale]);
|
|
447
449
|
const content = useMemo(() => {
|
|
448
450
|
if (renderThumbnail) return renderThumbnail(item, index);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { Animated, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { useTheme, createAnimatedValue } from "../../theme/index.js";
|
|
6
|
+
import { useControllableState } from "../../hooks/index.js";
|
|
6
7
|
import { triggerHaptic } from "../../utils/hapticUtils.js";
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
const sizeMap = {
|
|
@@ -26,6 +27,7 @@ const toneColor = (theme, tone) => {
|
|
|
26
27
|
const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
|
|
27
28
|
const {
|
|
28
29
|
checked,
|
|
30
|
+
defaultChecked,
|
|
29
31
|
onChange,
|
|
30
32
|
indeterminate = false,
|
|
31
33
|
disabled = false,
|
|
@@ -43,22 +45,29 @@ const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
43
45
|
testID,
|
|
44
46
|
...rest
|
|
45
47
|
} = props;
|
|
48
|
+
const [current, setCurrent] = useControllableState({
|
|
49
|
+
value: checked,
|
|
50
|
+
defaultValue: defaultChecked ?? false,
|
|
51
|
+
onChange
|
|
52
|
+
});
|
|
46
53
|
const theme = useTheme();
|
|
47
54
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
48
55
|
const boxSize = theme.components.checkbox?.[size]?.boxSize ?? sizeMap[size];
|
|
49
56
|
const checkboxBorderWidth = theme.colors.border.width;
|
|
50
57
|
const checkboxLabelGap = theme.components.checkbox?.labelGap ?? 10;
|
|
51
58
|
const fillColor = toneColor(theme, tone);
|
|
52
|
-
const isActive =
|
|
59
|
+
const isActive = current || indeterminate;
|
|
53
60
|
const progress = useRef(createAnimatedValue(isActive ? 1 : 0)).current;
|
|
54
61
|
useEffect(() => {
|
|
55
|
-
Animated.spring(progress, {
|
|
62
|
+
const anim = Animated.spring(progress, {
|
|
56
63
|
toValue: isActive ? 1 : 0,
|
|
57
64
|
damping: theme.motion.spring.snappy.damping,
|
|
58
65
|
stiffness: theme.motion.spring.snappy.stiffness,
|
|
59
66
|
mass: theme.motion.spring.snappy.mass,
|
|
60
67
|
useNativeDriver: true
|
|
61
|
-
})
|
|
68
|
+
});
|
|
69
|
+
anim.start();
|
|
70
|
+
return () => anim.stop();
|
|
62
71
|
}, [isActive, progress, theme.motion.spring.snappy]);
|
|
63
72
|
const scale = progress.interpolate({
|
|
64
73
|
inputRange: [0, 1],
|
|
@@ -67,7 +76,7 @@ const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
67
76
|
const handlePress = event => {
|
|
68
77
|
if (disabled) return;
|
|
69
78
|
if (haptic !== false && theme.components.checkbox?.pressHaptic) triggerHaptic(haptic);
|
|
70
|
-
|
|
79
|
+
setCurrent(!current);
|
|
71
80
|
rest.onPressOut?.(event);
|
|
72
81
|
};
|
|
73
82
|
const accessibleLabel = accessibilityLabel ?? label;
|
|
@@ -84,7 +93,7 @@ const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
84
93
|
accessibilityRole: "checkbox",
|
|
85
94
|
accessibilityLabel: accessibleLabel,
|
|
86
95
|
accessibilityState: {
|
|
87
|
-
checked: indeterminate ? 'mixed' :
|
|
96
|
+
checked: indeterminate ? 'mixed' : current,
|
|
88
97
|
disabled
|
|
89
98
|
},
|
|
90
99
|
testID: testID,
|
|
@@ -10,14 +10,14 @@ import { PickerTrigger } from "../PickerTrigger/index.js";
|
|
|
10
10
|
/**
|
|
11
11
|
* DatePicker operates in two modes:
|
|
12
12
|
*
|
|
13
|
-
* 1. **Controlled-modal mode** — pass `visible` (plus `
|
|
13
|
+
* 1. **Controlled-modal mode** — pass `visible` (plus `onChange` / `onClose`)
|
|
14
14
|
* and own the open state externally. The component renders only the modal.
|
|
15
15
|
* 2. **Trigger mode** — omit `visible`. The component renders a built-in
|
|
16
16
|
* PickerTrigger field (label / value / placeholder / chevron / clear /
|
|
17
17
|
* helper / error / size / variant) and manages its own open state. The
|
|
18
18
|
* field opens the modal on press and closes it on confirm/cancel.
|
|
19
19
|
*
|
|
20
|
-
* `
|
|
20
|
+
* `onChange` / `onClose` are typed optional to support trigger-only usage
|
|
21
21
|
* where the consumer may not need either callback. In controlled-modal mode
|
|
22
22
|
* they remain semantically required.
|
|
23
23
|
*/
|
|
@@ -99,7 +99,7 @@ const buildDecadeCells = anchorYear => {
|
|
|
99
99
|
const DatePicker = props => {
|
|
100
100
|
const {
|
|
101
101
|
value,
|
|
102
|
-
|
|
102
|
+
onChange,
|
|
103
103
|
onClose,
|
|
104
104
|
minDate,
|
|
105
105
|
maxDate,
|
|
@@ -174,7 +174,7 @@ const DatePicker = props => {
|
|
|
174
174
|
if (open) {
|
|
175
175
|
backdrop.setValue(0);
|
|
176
176
|
setNativeValue(sheet, 0);
|
|
177
|
-
Animated.parallel([Animated.timing(backdrop, {
|
|
177
|
+
const anim = Animated.parallel([Animated.timing(backdrop, {
|
|
178
178
|
toValue: 1,
|
|
179
179
|
duration: theme.motion.duration.normal,
|
|
180
180
|
easing: Easing.out(Easing.cubic),
|
|
@@ -185,7 +185,9 @@ const DatePicker = props => {
|
|
|
185
185
|
stiffness: theme.motion.spring.gentle.stiffness,
|
|
186
186
|
mass: theme.motion.spring.gentle.mass,
|
|
187
187
|
useNativeDriver: true
|
|
188
|
-
})])
|
|
188
|
+
})]);
|
|
189
|
+
anim.start();
|
|
190
|
+
return () => anim.stop();
|
|
189
191
|
}
|
|
190
192
|
}, [open, mode, backdrop, sheet, theme.motion]);
|
|
191
193
|
const disabledIsoSet = useMemo(() => {
|
|
@@ -385,9 +387,9 @@ const DatePicker = props => {
|
|
|
385
387
|
const handleConfirm = useCallback(() => {
|
|
386
388
|
if (!pendingDate) return;
|
|
387
389
|
if (theme.components.datePicker?.haptic) triggerHaptic('notificationSuccess');
|
|
388
|
-
|
|
390
|
+
onChange?.(pendingDate);
|
|
389
391
|
handleClose();
|
|
390
|
-
}, [handleClose,
|
|
392
|
+
}, [handleClose, onChange, pendingDate, theme.components.datePicker]);
|
|
391
393
|
const sheetTranslate = sheet.interpolate({
|
|
392
394
|
inputRange: [0, 1],
|
|
393
395
|
outputRange: [320, 0]
|
|
@@ -147,10 +147,11 @@ const DateRangePicker = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
147
147
|
// Modal open / close animation. Backdrop opacity uses JS driver — see
|
|
148
148
|
// Modal.tsx for the Fabric reason. Sheet transform stays native.
|
|
149
149
|
useEffect(() => {
|
|
150
|
+
let anim;
|
|
150
151
|
if (open) {
|
|
151
152
|
backdrop.setValue(0);
|
|
152
153
|
setNativeValue(sheet, 0);
|
|
153
|
-
Animated.parallel([Animated.timing(backdrop, {
|
|
154
|
+
anim = Animated.parallel([Animated.timing(backdrop, {
|
|
154
155
|
toValue: 1,
|
|
155
156
|
duration: theme.motion.duration.normal,
|
|
156
157
|
easing: Easing.out(Easing.cubic),
|
|
@@ -161,8 +162,10 @@ const DateRangePicker = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
161
162
|
stiffness: theme.motion.spring.gentle.stiffness,
|
|
162
163
|
mass: theme.motion.spring.gentle.mass,
|
|
163
164
|
useNativeDriver: true
|
|
164
|
-
})])
|
|
165
|
+
})]);
|
|
166
|
+
anim.start();
|
|
165
167
|
}
|
|
168
|
+
return () => anim?.stop();
|
|
166
169
|
}, [open, backdrop, sheet, theme.motion]);
|
|
167
170
|
const disabledIsoSet = useMemo(() => {
|
|
168
171
|
const set = new Set();
|
|
@@ -144,7 +144,7 @@ const tintForVariant = (theme, variant) => {
|
|
|
144
144
|
base: theme.colors.warning,
|
|
145
145
|
muted: theme.colors.background.secondary
|
|
146
146
|
};
|
|
147
|
-
case '
|
|
147
|
+
case 'error':
|
|
148
148
|
return {
|
|
149
149
|
base: theme.colors.error,
|
|
150
150
|
muted: theme.colors.background.secondary
|
|
@@ -175,7 +175,7 @@ const actionStyleFor = (theme, tone, variant) => {
|
|
|
175
175
|
textColor: theme.colors.text.inverse
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
-
case '
|
|
178
|
+
case 'error':
|
|
179
179
|
return {
|
|
180
180
|
backgroundColor: theme.colors.error,
|
|
181
181
|
borderColor: 'transparent',
|
|
@@ -286,20 +286,24 @@ export const FieldBase = props => {
|
|
|
286
286
|
const focusAnim = useRef(createAnimatedValue(focused ? 1 : 0)).current;
|
|
287
287
|
const errorAnim = useRef(createAnimatedValue(error ? 1 : 0)).current;
|
|
288
288
|
useEffect(() => {
|
|
289
|
-
Animated.timing(focusAnim, {
|
|
289
|
+
const anim = Animated.timing(focusAnim, {
|
|
290
290
|
toValue: focused ? 1 : 0,
|
|
291
291
|
duration: theme.motion.duration.fast,
|
|
292
292
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
293
293
|
useNativeDriver: false
|
|
294
|
-
})
|
|
294
|
+
});
|
|
295
|
+
anim.start();
|
|
296
|
+
return () => anim.stop();
|
|
295
297
|
}, [focused, focusAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
|
|
296
298
|
useEffect(() => {
|
|
297
|
-
Animated.timing(errorAnim, {
|
|
299
|
+
const anim = Animated.timing(errorAnim, {
|
|
298
300
|
toValue: error ? 1 : 0,
|
|
299
301
|
duration: theme.motion.duration.fast,
|
|
300
302
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
301
303
|
useNativeDriver: false
|
|
302
|
-
})
|
|
304
|
+
});
|
|
305
|
+
anim.start();
|
|
306
|
+
return () => anim.stop();
|
|
303
307
|
}, [error, errorAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
|
|
304
308
|
|
|
305
309
|
// Resting border + fill (pre-animation). Error wins over focus when both
|
|
@@ -90,12 +90,14 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
90
90
|
setNativeValue(hideAnim, 0);
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
Animated.timing(hideAnim, {
|
|
93
|
+
const anim = Animated.timing(hideAnim, {
|
|
94
94
|
toValue: isScrolling ? 1 : 0,
|
|
95
95
|
duration: theme.motion.duration.normal,
|
|
96
96
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
97
97
|
useNativeDriver: true
|
|
98
|
-
})
|
|
98
|
+
});
|
|
99
|
+
anim.start();
|
|
100
|
+
return () => anim.stop();
|
|
99
101
|
}, [hideOnScroll, isScrolling, hideAnim, theme.motion.duration.normal, theme.motion.easing.standard]);
|
|
100
102
|
const hideTranslateY = hideAnim.interpolate({
|
|
101
103
|
inputRange: [0, 1],
|
|
@@ -271,12 +273,13 @@ const FloatingActionButtonGroup = props => {
|
|
|
271
273
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
272
274
|
}, [actions.length]);
|
|
273
275
|
useEffect(() => {
|
|
274
|
-
Animated.timing(progress, {
|
|
276
|
+
const progressAnim = Animated.timing(progress, {
|
|
275
277
|
toValue: isOpen ? 1 : 0,
|
|
276
278
|
duration: theme.motion.duration.normal,
|
|
277
279
|
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
278
280
|
useNativeDriver: true
|
|
279
|
-
})
|
|
281
|
+
});
|
|
282
|
+
progressAnim.start();
|
|
280
283
|
const animations = itemAnims.current.map(value => Animated.timing(value, {
|
|
281
284
|
toValue: isOpen ? 1 : 0,
|
|
282
285
|
duration: theme.motion.duration.normal,
|
|
@@ -285,7 +288,12 @@ const FloatingActionButtonGroup = props => {
|
|
|
285
288
|
}));
|
|
286
289
|
// Reverse stagger order on close so items closest to primary collapse last
|
|
287
290
|
const ordered = isOpen ? animations : [...animations].reverse();
|
|
288
|
-
Animated.stagger(staggerMs, ordered)
|
|
291
|
+
const staggerAnim = Animated.stagger(staggerMs, ordered);
|
|
292
|
+
staggerAnim.start();
|
|
293
|
+
return () => {
|
|
294
|
+
progressAnim.stop();
|
|
295
|
+
staggerAnim.stop();
|
|
296
|
+
};
|
|
289
297
|
}, [isOpen, progress, theme.motion.duration.normal, theme.motion.easing.standard, actions.length, itemAnimsVersion, staggerMs]);
|
|
290
298
|
const setOpen = useCallback(next => {
|
|
291
299
|
if (pressHaptic) triggerHaptic('impactLight');
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { fontFor, useTheme, createAnimatedValue } from "../../theme/index.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useOptionalFormContext } from "../../form/FormContext.js";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
8
|
const FormField = /*#__PURE__*/forwardRef((props, ref) => {
|
|
8
9
|
const {
|
|
9
10
|
label,
|
|
10
11
|
helperText,
|
|
11
12
|
error,
|
|
12
13
|
required = false,
|
|
14
|
+
name,
|
|
13
15
|
children,
|
|
14
16
|
layout = 'stacked',
|
|
15
17
|
labelStyle,
|
|
@@ -22,19 +24,32 @@ const FormField = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
22
24
|
} = props;
|
|
23
25
|
const theme = useTheme();
|
|
24
26
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
25
|
-
const
|
|
27
|
+
const formCtx = useOptionalFormContext();
|
|
28
|
+
const isConnected = name != null && formCtx != null;
|
|
29
|
+
const field = isConnected ? formCtx.getFieldProps(name) : null;
|
|
30
|
+
const isRenderProp = typeof children === 'function';
|
|
31
|
+
|
|
32
|
+
// Error the FormField renders itself. When connected to an element child the
|
|
33
|
+
// child (Input/Select etc.) shows the injected error, so FormField stays
|
|
34
|
+
// quiet to avoid a duplicate message; the render-prop form keeps it here.
|
|
35
|
+
const ownError = isConnected ? isRenderProp ? field?.error : undefined : error;
|
|
36
|
+
// Error used purely for assistive tech — always reflects the real error.
|
|
37
|
+
const a11yError = isConnected ? field?.error : error;
|
|
38
|
+
const errorOpacity = useRef(createAnimatedValue(ownError ? 1 : 0)).current;
|
|
26
39
|
useEffect(() => {
|
|
27
|
-
Animated.timing(errorOpacity, {
|
|
28
|
-
toValue:
|
|
29
|
-
duration:
|
|
40
|
+
const anim = Animated.timing(errorOpacity, {
|
|
41
|
+
toValue: ownError ? 1 : 0,
|
|
42
|
+
duration: theme.motion.duration.fast,
|
|
30
43
|
useNativeDriver: true
|
|
31
|
-
})
|
|
32
|
-
|
|
44
|
+
});
|
|
45
|
+
anim.start();
|
|
46
|
+
return () => anim.stop();
|
|
47
|
+
}, [ownError, errorOpacity, theme.motion.duration.fast]);
|
|
33
48
|
const computedAccessibilityLabel = useMemo(() => {
|
|
34
49
|
if (accessibilityLabel) return accessibilityLabel;
|
|
35
50
|
if (!label) return undefined;
|
|
36
|
-
return
|
|
37
|
-
}, [accessibilityLabel, label,
|
|
51
|
+
return a11yError ? `${label}, error: ${a11yError}` : label;
|
|
52
|
+
}, [accessibilityLabel, label, a11yError]);
|
|
38
53
|
const labelNode = label ? /*#__PURE__*/_jsxs(Text, {
|
|
39
54
|
style: [styles.label, layout === 'inline' ? styles.labelInline : null, labelStyle],
|
|
40
55
|
children: [label, required ? /*#__PURE__*/_jsx(Text, {
|
|
@@ -43,39 +58,60 @@ const FormField = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
43
58
|
}) : null]
|
|
44
59
|
}) : null;
|
|
45
60
|
const inputContainerStyle = layout === 'inline' ? styles.inputInline : styles.inputStacked;
|
|
46
|
-
const showError = Boolean(
|
|
61
|
+
const showError = Boolean(ownError);
|
|
47
62
|
const showHelper = !showError && Boolean(helperText);
|
|
63
|
+
const renderedChildren = (() => {
|
|
64
|
+
if (isRenderProp) {
|
|
65
|
+
return field ? children(field) : null;
|
|
66
|
+
}
|
|
67
|
+
if (isConnected && field && /*#__PURE__*/React.isValidElement(children)) {
|
|
68
|
+
const child = children;
|
|
69
|
+
const childOnChangeText = child.props.onChangeText;
|
|
70
|
+
const childOnBlur = child.props.onBlur;
|
|
71
|
+
const childOnFocus = child.props.onFocus;
|
|
72
|
+
return /*#__PURE__*/React.cloneElement(child, {
|
|
73
|
+
value: field.value,
|
|
74
|
+
onChangeText: text => {
|
|
75
|
+
field.onChangeText(text);
|
|
76
|
+
childOnChangeText?.(text);
|
|
77
|
+
},
|
|
78
|
+
onBlur: e => {
|
|
79
|
+
field.onBlur();
|
|
80
|
+
childOnBlur?.(e);
|
|
81
|
+
},
|
|
82
|
+
onFocus: e => {
|
|
83
|
+
field.onFocus();
|
|
84
|
+
childOnFocus?.(e);
|
|
85
|
+
},
|
|
86
|
+
error: field.error,
|
|
87
|
+
ref: node => formCtx.registerField(name, node ?? null)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return children;
|
|
91
|
+
})();
|
|
48
92
|
return /*#__PURE__*/_jsxs(View, {
|
|
49
93
|
ref: ref,
|
|
50
94
|
style: [layout === 'inline' ? styles.rootInline : styles.rootStacked, containerStyle],
|
|
51
95
|
accessibilityLabel: computedAccessibilityLabel
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// error message itself is folded into the label and hint.
|
|
96
|
+
// RN's AccessibilityState type has no `invalid` field, so the error is
|
|
97
|
+
// folded into the label and surfaced again via the hint.
|
|
55
98
|
,
|
|
56
|
-
accessibilityHint:
|
|
99
|
+
accessibilityHint: a11yError ? a11yError : undefined,
|
|
57
100
|
testID: testID,
|
|
58
|
-
children: [
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
children: children
|
|
62
|
-
})]
|
|
63
|
-
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
64
|
-
children: [labelNode, /*#__PURE__*/_jsx(View, {
|
|
65
|
-
style: [inputContainerStyle, inputContainerStyleProp],
|
|
66
|
-
children: children
|
|
67
|
-
})]
|
|
101
|
+
children: [labelNode, /*#__PURE__*/_jsx(View, {
|
|
102
|
+
style: [inputContainerStyle, inputContainerStyleProp],
|
|
103
|
+
children: renderedChildren
|
|
68
104
|
}), showHelper ? /*#__PURE__*/_jsx(Text, {
|
|
69
105
|
style: [styles.helper, helperStyle],
|
|
70
106
|
numberOfLines: 2,
|
|
71
107
|
children: helperText
|
|
72
|
-
}) : null,
|
|
108
|
+
}) : null, ownError ? /*#__PURE__*/_jsx(Animated.Text, {
|
|
73
109
|
style: [styles.error, {
|
|
74
110
|
opacity: errorOpacity
|
|
75
111
|
}, errorStyle],
|
|
76
112
|
numberOfLines: 2,
|
|
77
113
|
accessibilityLiveRegion: "polite",
|
|
78
|
-
children:
|
|
114
|
+
children: ownError
|
|
79
115
|
}) : null]
|
|
80
116
|
});
|
|
81
117
|
});
|
|
@@ -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
|