@webority-technologies/mobile 0.0.21 → 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 +69 -13
- package/lib/commonjs/components/BottomSheet/index.js +6 -0
- 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 +141 -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 +68 -13
- package/lib/module/components/BottomSheet/index.js +1 -1
- 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 +5 -1
- 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/BottomSheet/BottomSheet.d.ts +41 -0
- package/lib/typescript/commonjs/components/BottomSheet/index.d.ts +2 -2
- 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 +10 -2
- 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/BottomSheet/BottomSheet.d.ts +41 -0
- package/lib/typescript/module/components/BottomSheet/index.d.ts +2 -2
- 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 +10 -2
- 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
|
@@ -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
|
|
@@ -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
|