react-native-molecules 0.5.0-beta.3 → 0.5.0-beta.31
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/components/Accordion/Accordion.tsx +2 -6
- package/components/Accordion/AccordionItem.tsx +16 -12
- package/components/Accordion/AccordionItemContent.tsx +6 -1
- package/components/Accordion/AccordionItemHeader.tsx +1 -1
- package/components/Accordion/utils.ts +6 -0
- package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
- package/components/Appbar/AppbarBase.tsx +18 -13
- package/components/Button/Button.tsx +211 -264
- package/components/Button/index.tsx +9 -3
- package/components/Button/types.ts +16 -2
- package/components/Button/utils.ts +230 -208
- package/components/Card/Card.tsx +1 -1
- package/components/Checkbox/Checkbox.tsx +125 -88
- package/components/Checkbox/CheckboxBase.ios.tsx +14 -23
- package/components/Checkbox/CheckboxBase.tsx +21 -137
- package/components/Checkbox/context.tsx +14 -0
- package/components/Checkbox/index.tsx +11 -4
- package/components/Checkbox/types.ts +63 -29
- package/components/Checkbox/utils.ts +25 -108
- package/components/Chip/Chip.tsx +41 -52
- package/components/Chip/utils.ts +3 -7
- package/components/DateField/DateField.tsx +111 -0
- package/components/DateField/index.tsx +6 -0
- package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +19 -51
- package/components/DatePicker/DateCalendar.tsx +83 -0
- package/components/DatePicker/DatePickerActions.tsx +73 -0
- package/components/DatePicker/DatePickerModal.tsx +246 -0
- package/components/DatePicker/DatePickerPopover.tsx +79 -0
- package/components/DatePicker/DatePickerProvider.tsx +158 -0
- package/components/DatePicker/DatePickerTrigger.tsx +23 -0
- package/components/DatePicker/context.tsx +83 -0
- package/components/DatePicker/index.tsx +45 -0
- package/components/DatePicker/utils.ts +295 -0
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +117 -0
- package/components/DatePickerInline/DatePickerInline.tsx +17 -16
- package/components/DatePickerInline/DatePickerInlineBase.tsx +11 -5
- package/components/DatePickerInline/DatePickerInlineHeader.tsx +50 -20
- package/components/DatePickerInline/Day.tsx +25 -1
- package/components/DatePickerInline/DayNames.tsx +13 -10
- package/components/DatePickerInline/DayRange.tsx +2 -4
- package/components/DatePickerInline/HeaderItem.tsx +44 -29
- package/components/DatePickerInline/Month.tsx +48 -67
- package/components/DatePickerInline/MonthPicker.tsx +80 -92
- package/components/DatePickerInline/Swiper.native.tsx +21 -4
- package/components/DatePickerInline/Swiper.tsx +169 -14
- package/components/DatePickerInline/SwiperUtils.ts +1 -1
- package/components/DatePickerInline/Week.tsx +6 -1
- package/components/DatePickerInline/YearPicker.tsx +220 -78
- package/components/DatePickerInline/dateUtils.tsx +18 -13
- package/components/DatePickerInline/store.tsx +27 -0
- package/components/DatePickerInline/types.ts +6 -2
- package/components/DatePickerInline/utils.ts +66 -29
- package/components/Divider/Divider.tsx +192 -0
- package/components/Divider/index.tsx +10 -0
- package/components/Drawer/Drawer.tsx +17 -6
- package/components/Drawer/DrawerItemGroup.tsx +3 -7
- package/components/ElementGroup/ElementGroup.tsx +1 -1
- package/components/FilePicker/FilePicker.tsx +48 -78
- package/components/FilePicker/index.tsx +2 -1
- package/components/FilePicker/utils.ts +9 -0
- package/components/HelperText/HelperText.tsx +0 -35
- package/components/Icon/iconFactory.tsx +5 -4
- package/components/Icon/index.tsx +1 -1
- package/components/Icon/types.ts +17 -6
- package/components/IconButton/IconButton.tsx +84 -84
- package/components/IconButton/index.tsx +1 -0
- package/components/IconButton/types.ts +10 -0
- package/components/IconButton/utils.ts +167 -33
- package/components/List/List.tsx +276 -0
- package/components/List/context.tsx +27 -0
- package/components/List/index.ts +8 -0
- package/components/List/types.ts +117 -0
- package/components/List/utils.ts +79 -0
- package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
- package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
- package/components/LoadingIndicator/index.tsx +13 -0
- package/components/LoadingIndicator/utils.ts +117 -0
- package/components/Menu/Menu.tsx +162 -39
- package/components/Menu/index.tsx +10 -7
- package/components/Menu/utils.ts +21 -70
- package/components/NavigationRail/NavigationRail.tsx +15 -9
- package/components/Popover/Popover.tsx +119 -145
- package/components/Popover/PopoverRoot.tsx +60 -0
- package/components/Popover/common.ts +54 -34
- package/components/Popover/index.ts +12 -1
- package/components/Popover/usePlatformMeasure.native.ts +90 -0
- package/components/Popover/usePlatformMeasure.ts +120 -0
- package/components/Popover/utils.ts +34 -0
- package/components/Portal/Portal.tsx +1 -2
- package/components/Radio/Radio.tsx +188 -0
- package/components/Radio/RadioBase.ios.tsx +69 -0
- package/components/Radio/RadioBase.tsx +136 -0
- package/components/Radio/context.tsx +23 -0
- package/components/Radio/index.tsx +20 -0
- package/components/Radio/types.ts +101 -0
- package/components/Radio/utils.ts +115 -0
- package/components/Rating/Rating.tsx +1 -1
- package/components/Select/Select.tsx +521 -785
- package/components/Select/context.tsx +81 -0
- package/components/Select/index.ts +26 -14
- package/components/Select/types.ts +65 -58
- package/components/Select/utils.ts +126 -0
- package/components/Slot/Slot.tsx +224 -0
- package/components/Slot/compose-refs.tsx +62 -0
- package/components/Slot/index.tsx +8 -0
- package/components/Surface/Surface.android.tsx +32 -7
- package/components/Surface/Surface.ios.tsx +34 -29
- package/components/Surface/Surface.tsx +31 -4
- package/components/Surface/utils.ts +44 -6
- package/components/Switch/Switch.ios.tsx +1 -1
- package/components/Switch/Switch.tsx +10 -3
- package/components/Tabs/TabItem.tsx +35 -58
- package/components/Tabs/TabLabel.tsx +5 -9
- package/components/Tabs/Tabs.tsx +156 -150
- package/components/Tabs/utils.ts +15 -2
- package/components/Text/textFactory.tsx +17 -5
- package/components/TextInput/TextInput.tsx +663 -579
- package/components/TextInput/index.tsx +19 -3
- package/components/TextInput/types.ts +77 -28
- package/components/TextInput/utils.ts +235 -145
- package/components/TimeField/TimeField.tsx +75 -0
- package/components/TimeField/index.tsx +6 -0
- package/components/TimeField/useTimeFieldState.ts +70 -0
- package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
- package/components/TimePicker/AnalogClock.tsx +1 -1
- package/components/TimePicker/TimeInput.tsx +87 -42
- package/components/TimePicker/TimeInputs.tsx +138 -50
- package/components/TimePicker/TimePicker.tsx +74 -11
- package/components/TimePicker/TimePickerModal.tsx +186 -0
- package/components/TimePicker/context.tsx +17 -0
- package/components/TimePicker/index.tsx +15 -3
- package/components/TimePicker/utils.ts +93 -4
- package/components/Tooltip/Tooltip.tsx +42 -67
- package/components/Tooltip/TooltipContent.tsx +32 -5
- package/components/Tooltip/TooltipTrigger.tsx +21 -24
- package/components/Tooltip/index.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.native.tsx +83 -16
- package/components/TouchableRipple/TouchableRipple.tsx +150 -102
- package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
- package/hocs/index.tsx +1 -1
- package/hocs/withKeyboardAccessibility.tsx +2 -3
- package/hocs/withPortal.tsx +1 -1
- package/hooks/index.tsx +2 -12
- package/hooks/useActionState.tsx +19 -8
- package/hooks/useContrastColor.ts +1 -2
- package/hooks/useFilePicker.tsx +7 -17
- package/hooks/useHandleNumberFormat.tsx +2 -2
- package/hooks/useMediaQuery.tsx +1 -2
- package/package.json +95 -111
- package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +6 -3
- package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
- package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
- package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
- package/styles/shadow.ts +2 -1
- package/styles/themes/LightTheme.tsx +1 -1
- package/utils/DocumentPicker/documentPicker.ts +78 -27
- package/utils/DocumentPicker/types.ts +0 -1
- package/utils/extractSubcomponents.ts +89 -0
- package/utils/extractTextStyles.ts +1 -2
- package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
- package/utils/index.ts +0 -3
- package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
- package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
- package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
- package/components/DatePickerDocked/index.tsx +0 -17
- package/components/DatePickerDocked/types.ts +0 -11
- package/components/DatePickerDocked/utils.ts +0 -157
- package/components/DatePickerInline/DatePickerContext.tsx +0 -21
- package/components/DatePickerInput/DatePickerInput.tsx +0 -139
- package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
- package/components/DatePickerInput/DateRangeInput.tsx +0 -88
- package/components/DatePickerInput/index.tsx +0 -10
- package/components/DatePickerInput/types.ts +0 -28
- package/components/DatePickerInput/utils.ts +0 -15
- package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
- package/components/DatePickerModal/CalendarEdit.tsx +0 -139
- package/components/DatePickerModal/DatePickerModal.tsx +0 -85
- package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
- package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
- package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
- package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
- package/components/DatePickerModal/index.tsx +0 -16
- package/components/DatePickerModal/types.ts +0 -92
- package/components/DatePickerModal/utils.ts +0 -122
- package/components/DateTimePicker/DateTimePicker.tsx +0 -172
- package/components/DateTimePicker/index.tsx +0 -10
- package/components/DateTimePicker/utils.ts +0 -12
- package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
- package/components/HorizontalDivider/index.tsx +0 -9
- package/components/ListItem/ListItem.tsx +0 -136
- package/components/ListItem/ListItemDescription.tsx +0 -25
- package/components/ListItem/ListItemTitle.tsx +0 -25
- package/components/ListItem/index.tsx +0 -14
- package/components/ListItem/utils.ts +0 -115
- package/components/Menu/MenuDivider.tsx +0 -13
- package/components/Menu/MenuItem.tsx +0 -128
- package/components/Popover/Popover.native.tsx +0 -185
- package/components/RadioButton/RadioButton.tsx +0 -138
- package/components/RadioButton/RadioButtonAndroid.tsx +0 -188
- package/components/RadioButton/RadioButtonGroup.tsx +0 -98
- package/components/RadioButton/RadioButtonIOS.tsx +0 -106
- package/components/RadioButton/RadioButtonItem.tsx +0 -232
- package/components/RadioButton/index.ts +0 -22
- package/components/RadioButton/utils.ts +0 -165
- package/components/TimePickerField/TimePickerField.tsx +0 -152
- package/components/TimePickerField/index.tsx +0 -10
- package/components/TimePickerField/utils.ts +0 -94
- package/components/TimePickerModal/TimePickerModal.tsx +0 -115
- package/components/TimePickerModal/index.tsx +0 -10
- package/components/TimePickerModal/utils.ts +0 -47
- package/components/VerticalDivider/VerticalDivider.tsx +0 -100
- package/components/VerticalDivider/index.tsx +0 -9
- package/context-bridge/index.tsx +0 -87
- package/fast-context/index.tsx +0 -190
- package/hocs/typedMemo.tsx +0 -5
- package/hooks/useControlledValue.tsx +0 -68
- package/hooks/useLatest.tsx +0 -9
- package/hooks/useMergedRefs.ts +0 -14
- package/hooks/usePrevious.ts +0 -13
- package/hooks/useSearchable.tsx +0 -74
- package/hooks/useSubcomponents.tsx +0 -59
- package/hooks/useToggle.tsx +0 -24
- package/utils/color.ts +0 -22
- package/utils/compare/index.ts +0 -54
- package/utils/lodash.ts +0 -49
- package/utils/repository.ts +0 -53
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { useControlledValue, useLatest } from '@react-native-molecules/utils/hooks';
|
|
1
2
|
import React, {
|
|
2
|
-
forwardRef,
|
|
3
3
|
memo,
|
|
4
4
|
type PropsWithoutRef,
|
|
5
5
|
type ReactNode,
|
|
6
|
-
type
|
|
6
|
+
type Ref,
|
|
7
7
|
useCallback,
|
|
8
8
|
useContext,
|
|
9
9
|
useEffect,
|
|
@@ -14,28 +14,52 @@ import React, {
|
|
|
14
14
|
} from 'react';
|
|
15
15
|
import type {
|
|
16
16
|
BlurEvent,
|
|
17
|
+
ColorValue,
|
|
17
18
|
FocusEvent,
|
|
19
|
+
GestureResponderEvent,
|
|
18
20
|
LayoutChangeEvent,
|
|
19
21
|
StyleProp,
|
|
20
|
-
TextInputProps,
|
|
21
|
-
TextStyle,
|
|
22
|
+
TextInputProps as NativeTextInputProps,
|
|
22
23
|
ViewProps,
|
|
23
24
|
ViewStyle,
|
|
24
25
|
} from 'react-native';
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
import {
|
|
27
|
+
Animated,
|
|
28
|
+
I18nManager,
|
|
29
|
+
Platform,
|
|
30
|
+
Pressable,
|
|
31
|
+
TextInput as NativeTextInput,
|
|
32
|
+
View,
|
|
33
|
+
} from 'react-native';
|
|
34
|
+
|
|
35
|
+
import { useActionState } from '../../hooks';
|
|
33
36
|
import { createSyntheticEvent, resolveStateVariant } from '../../utils';
|
|
37
|
+
import { extractSubcomponents } from '../../utils/extractSubcomponents';
|
|
34
38
|
import { HelperText } from '../HelperText';
|
|
39
|
+
import { Icon } from '../Icon';
|
|
35
40
|
import { StateLayer } from '../StateLayer';
|
|
36
41
|
import InputLabel from './InputLabel';
|
|
37
|
-
import type {
|
|
38
|
-
|
|
42
|
+
import type {
|
|
43
|
+
RenderProps,
|
|
44
|
+
TextInputElementCompoundProps,
|
|
45
|
+
TextInputIconCompoundProps,
|
|
46
|
+
TextInputLabelCompoundProps,
|
|
47
|
+
TextInputOutlineCompoundProps,
|
|
48
|
+
TextInputSize,
|
|
49
|
+
TextInputSupportingTextCompoundProps,
|
|
50
|
+
TextInputVariant,
|
|
51
|
+
} from './types';
|
|
52
|
+
import {
|
|
53
|
+
getInputMinHeight,
|
|
54
|
+
styles,
|
|
55
|
+
TextInputContext,
|
|
56
|
+
textInputIconStyles,
|
|
57
|
+
textInputLabelStyles,
|
|
58
|
+
textInputLeftStyles,
|
|
59
|
+
textInputOutlineStyles,
|
|
60
|
+
textInputRightStyles,
|
|
61
|
+
textInputSupportingTextStyles,
|
|
62
|
+
} from './utils';
|
|
39
63
|
|
|
40
64
|
const BLUR_ANIMATION_DURATION = 180;
|
|
41
65
|
const FOCUS_ANIMATION_DURATION = 150;
|
|
@@ -46,138 +70,100 @@ export type ElementProps = {
|
|
|
46
70
|
focused: boolean;
|
|
47
71
|
};
|
|
48
72
|
|
|
49
|
-
type
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
render?: (props: RenderProps) => ReactNode;
|
|
146
|
-
/**
|
|
147
|
-
* Value of the text input.
|
|
148
|
-
*/
|
|
149
|
-
value?: string;
|
|
150
|
-
/**
|
|
151
|
-
* Pass `fontSize` prop to modify the font size inside `TextInput`.
|
|
152
|
-
* Pass `height` prop to set `TextInput` height.
|
|
153
|
-
* Pass `backgroundColor` prop to set `TextInput` backgroundColor.
|
|
154
|
-
*/
|
|
155
|
-
style?: StyleProp<TextStyle>;
|
|
156
|
-
/**
|
|
157
|
-
* Style of the Input Container
|
|
158
|
-
*/
|
|
159
|
-
inputContainerStyle?: StyleProp<ViewStyle>;
|
|
160
|
-
/**
|
|
161
|
-
* Style of the Input
|
|
162
|
-
*/
|
|
163
|
-
inputStyle?: StyleProp<TextStyle>;
|
|
164
|
-
/**
|
|
165
|
-
* Style of the rightElement
|
|
166
|
-
*/
|
|
167
|
-
rightElementStyle?: StyleProp<TextStyle>;
|
|
168
|
-
/**
|
|
169
|
-
* Style of the leftElement
|
|
170
|
-
*/
|
|
171
|
-
leftElementStyle?: StyleProp<TextStyle>;
|
|
172
|
-
/**
|
|
173
|
-
* props for the stateLayer
|
|
174
|
-
*/
|
|
175
|
-
stateLayerProps?: PropsWithoutRef<ViewProps>;
|
|
176
|
-
/**
|
|
177
|
-
* testID to be used on tests.
|
|
178
|
-
*/
|
|
179
|
-
testID?: string;
|
|
180
|
-
};
|
|
73
|
+
export type Props = Omit<NativeTextInputProps, 'style' | 'ref'> & {
|
|
74
|
+
ref?: Ref<TextInputHandles | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Variant of the TextInput.
|
|
77
|
+
* - `flat` - flat input with an underline.
|
|
78
|
+
* - `outlined` - input with an outline.
|
|
79
|
+
* - `plain` - plain input without any decoration.
|
|
80
|
+
*/
|
|
81
|
+
variant?: TextInputVariant;
|
|
82
|
+
/**
|
|
83
|
+
* Size of the TextInput.
|
|
84
|
+
*/
|
|
85
|
+
size?: TextInputSize;
|
|
86
|
+
/**
|
|
87
|
+
* If true, user won't be able to interact with the component.
|
|
88
|
+
*/
|
|
89
|
+
disabled?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Whether to style the TextInput with error style.
|
|
92
|
+
*/
|
|
93
|
+
error?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the input can have multiple lines.
|
|
96
|
+
*/
|
|
97
|
+
multiline?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* To display the required indicator in the Label
|
|
100
|
+
*/
|
|
101
|
+
required?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Value of the text input (for controlled usage).
|
|
104
|
+
*/
|
|
105
|
+
value?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Default value for uncontrolled usage.
|
|
108
|
+
*/
|
|
109
|
+
defaultValue?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Callback that is called when the text input's text changes.
|
|
112
|
+
*/
|
|
113
|
+
onChangeText?: (text: string) => void;
|
|
114
|
+
/**
|
|
115
|
+
* Callback when input is focused.
|
|
116
|
+
*/
|
|
117
|
+
onFocus?: (args: FocusEvent) => void;
|
|
118
|
+
/**
|
|
119
|
+
* Callback when input loses focus.
|
|
120
|
+
*/
|
|
121
|
+
onBlur?: (args: BlurEvent) => void;
|
|
122
|
+
/**
|
|
123
|
+
* Selection color of the input
|
|
124
|
+
*/
|
|
125
|
+
selectionColor?: ColorValue;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Placeholder text color.
|
|
129
|
+
*/
|
|
130
|
+
placeholderTextColor?: ColorValue;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* If false, text is not editable.
|
|
134
|
+
*/
|
|
135
|
+
editable?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Style of the container.
|
|
138
|
+
*/
|
|
139
|
+
style?: StyleProp<ViewStyle>;
|
|
140
|
+
/**
|
|
141
|
+
* Props for the container that is directly inside the context container which is horizontal layout
|
|
142
|
+
*/
|
|
143
|
+
containerProps?: ViewProps;
|
|
144
|
+
/**
|
|
145
|
+
* Style of the Input Container
|
|
146
|
+
*/
|
|
147
|
+
inputWrapperProps?: ViewProps;
|
|
148
|
+
/**
|
|
149
|
+
* props for the stateLayer (flat variant)
|
|
150
|
+
*/
|
|
151
|
+
stateLayerProps?: PropsWithoutRef<ViewProps>;
|
|
152
|
+
/**
|
|
153
|
+
* testID to be used on tests.
|
|
154
|
+
*/
|
|
155
|
+
testID?: string;
|
|
156
|
+
/**
|
|
157
|
+
* Children for composable API. Use TextInput.Label, TextInput.Left, TextInput.Right, etc.
|
|
158
|
+
*/
|
|
159
|
+
children?: ReactNode;
|
|
160
|
+
/**
|
|
161
|
+
* Render custom input component.
|
|
162
|
+
*/
|
|
163
|
+
render?: (props: RenderProps) => ReactNode;
|
|
164
|
+
inputStyle?: NativeTextInputProps['style'];
|
|
165
|
+
placeholder?: string;
|
|
166
|
+
};
|
|
181
167
|
|
|
182
168
|
export type TextInputHandles = Pick<
|
|
183
169
|
NativeTextInput,
|
|
@@ -191,504 +177,602 @@ const labelWiggleXOffset = 4;
|
|
|
191
177
|
|
|
192
178
|
const DefaultComponent = (props: RenderProps) => <NativeTextInput {...props} />;
|
|
193
179
|
|
|
194
|
-
const TextInput =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
180
|
+
const TextInput = ({
|
|
181
|
+
ref,
|
|
182
|
+
variant = 'outlined',
|
|
183
|
+
size = 'sm',
|
|
184
|
+
disabled = false,
|
|
185
|
+
error: errorProp = false,
|
|
186
|
+
multiline = false,
|
|
187
|
+
editable = true,
|
|
188
|
+
required = false,
|
|
189
|
+
|
|
190
|
+
selectionColor,
|
|
191
|
+
placeholderTextColor,
|
|
192
|
+
containerProps,
|
|
193
|
+
style,
|
|
194
|
+
inputWrapperProps,
|
|
195
|
+
stateLayerProps,
|
|
196
|
+
onBlur,
|
|
197
|
+
onFocus,
|
|
198
|
+
testID,
|
|
199
|
+
value: valueProp,
|
|
200
|
+
defaultValue,
|
|
201
|
+
onChangeText,
|
|
202
|
+
render,
|
|
203
|
+
placeholder,
|
|
204
|
+
children,
|
|
205
|
+
inputStyle,
|
|
206
|
+
...rest
|
|
207
|
+
}: Props) => {
|
|
208
|
+
const { hovered, actionsRef } = useActionState({ actionsToListen: ['hover'] });
|
|
209
|
+
|
|
210
|
+
const {
|
|
211
|
+
TextInput_Label,
|
|
212
|
+
TextInput_Left,
|
|
213
|
+
TextInput_Right,
|
|
214
|
+
TextInput_SupportingText,
|
|
215
|
+
TextInput_Outline,
|
|
216
|
+
rest: restChildren,
|
|
217
|
+
} = extractSubcomponents({
|
|
218
|
+
children,
|
|
219
|
+
allowedChildren: [
|
|
220
|
+
{ name: 'TextInput_Label', allowMultiple: false },
|
|
221
|
+
{ name: 'TextInput_Left', allowMultiple: false },
|
|
222
|
+
{ name: 'TextInput_Right', allowMultiple: false },
|
|
223
|
+
{ name: 'TextInput_SupportingText', allowMultiple: false },
|
|
224
|
+
{ name: 'TextInput_Outline', allowMultiple: false },
|
|
225
|
+
] as const,
|
|
226
|
+
includeRest: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const hasLabel = TextInput_Label.length > 0;
|
|
230
|
+
|
|
231
|
+
const [focused, setFocused] = useState<boolean>(false);
|
|
232
|
+
// Use value from props instead of local state when input is controlled
|
|
233
|
+
const [value, onChangeValue] = useControlledValue({
|
|
234
|
+
value: valueProp,
|
|
235
|
+
defaultValue: defaultValue,
|
|
236
|
+
onChange: onChangeText,
|
|
237
|
+
disabled: !editable || disabled,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const onBlurRef = useLatest(onBlur);
|
|
241
|
+
|
|
242
|
+
const state = resolveStateVariant({
|
|
243
|
+
errorDisabled: errorProp && disabled,
|
|
244
|
+
disabled,
|
|
245
|
+
errorFocusedAndHovered: errorProp && hovered && focused,
|
|
246
|
+
errorFocused: errorProp && focused,
|
|
247
|
+
errorHovered: errorProp && hovered,
|
|
248
|
+
hoveredAndFocused: hovered && focused,
|
|
249
|
+
hovered,
|
|
250
|
+
focused: focused,
|
|
251
|
+
error: !!errorProp,
|
|
252
|
+
}) as any;
|
|
253
|
+
|
|
254
|
+
styles.useVariants({
|
|
255
|
+
variant: variant as any,
|
|
256
|
+
state,
|
|
257
|
+
size,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const [labelLayout, setLabelLayout] = useState<{
|
|
261
|
+
measured: boolean;
|
|
262
|
+
width: number;
|
|
263
|
+
height: number;
|
|
264
|
+
}>({
|
|
265
|
+
measured: false,
|
|
266
|
+
width: 0,
|
|
267
|
+
height: 0,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const [leftElementLayout, setElementLayout] = useState<{
|
|
271
|
+
measured: boolean;
|
|
272
|
+
width: number;
|
|
273
|
+
height: number;
|
|
274
|
+
}>({
|
|
275
|
+
measured: false,
|
|
276
|
+
width: 0,
|
|
277
|
+
height: 0,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const inputRefLocal = useRef<NativeTextInput>(null);
|
|
281
|
+
|
|
282
|
+
useImperativeHandle(ref, () => inputRefLocal.current!);
|
|
283
|
+
|
|
284
|
+
const handleFocus = useCallback(
|
|
285
|
+
(args: FocusEvent) => {
|
|
286
|
+
if (disabled || !editable) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
230
289
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
290
|
+
setFocused(true);
|
|
291
|
+
|
|
292
|
+
onFocus?.(args);
|
|
293
|
+
},
|
|
294
|
+
[disabled, editable, onFocus],
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const handleBlur = useCallback(
|
|
298
|
+
(args: BlurEvent) => {
|
|
299
|
+
if (!editable) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
setFocused(false);
|
|
304
|
+
onBlur?.(args);
|
|
305
|
+
},
|
|
306
|
+
[editable, onBlur],
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const handleLayoutAnimatedText = useCallback((e: LayoutChangeEvent) => {
|
|
310
|
+
setLabelLayout({
|
|
311
|
+
width: e.nativeEvent.layout.width,
|
|
312
|
+
height: e.nativeEvent.layout.height,
|
|
313
|
+
measured: true,
|
|
245
314
|
});
|
|
315
|
+
}, []);
|
|
246
316
|
|
|
247
|
-
|
|
317
|
+
const handleLayoutLeftElement = useCallback((e: LayoutChangeEvent) => {
|
|
318
|
+
setElementLayout({
|
|
319
|
+
width: e.nativeEvent.layout.width,
|
|
320
|
+
height: e.nativeEvent.layout.height,
|
|
321
|
+
measured: true,
|
|
322
|
+
});
|
|
323
|
+
}, []);
|
|
248
324
|
|
|
249
|
-
|
|
250
|
-
errorDisabled: errorProp && disabled,
|
|
251
|
-
disabled,
|
|
252
|
-
errorFocusedAndHovered: errorProp && hovered && focused,
|
|
253
|
-
errorFocused: errorProp && focused,
|
|
254
|
-
errorHovered: errorProp && hovered,
|
|
255
|
-
hoveredAndFocused: hovered && focused,
|
|
256
|
-
hovered,
|
|
257
|
-
focused: focused,
|
|
258
|
-
error: !!errorProp,
|
|
259
|
-
}) as any;
|
|
325
|
+
const forceFocus = useCallback(() => inputRefLocal.current?.focus(), []);
|
|
260
326
|
|
|
261
|
-
|
|
262
|
-
|
|
327
|
+
const inputMinHeight = getInputMinHeight(variant, size);
|
|
328
|
+
|
|
329
|
+
// Workaround for React bug where onBlur doesn't fire when a focused input unmounts
|
|
330
|
+
// Issue: https://github.com/facebook/react/issues/25194
|
|
331
|
+
// Only needed for React 18+ on web (issue still exists in React 19)
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
const is_version_18_or_higher =
|
|
334
|
+
typeof React.version === 'string' ? +React.version.split('.')[0] >= 18 : false;
|
|
335
|
+
|
|
336
|
+
const _onBlurRef = onBlurRef;
|
|
337
|
+
const input = inputRefLocal.current;
|
|
338
|
+
|
|
339
|
+
return () => {
|
|
340
|
+
if (!is_version_18_or_higher || !focused || Platform.OS !== 'web') {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Manually fire blur event on unmount if input was focused
|
|
345
|
+
const event = new Event('blur', { bubbles: true });
|
|
346
|
+
Object.defineProperty(event, 'target', {
|
|
347
|
+
writable: false,
|
|
348
|
+
value: input,
|
|
349
|
+
});
|
|
350
|
+
const syntheticEvent = createSyntheticEvent(
|
|
351
|
+
event,
|
|
352
|
+
) as React.ChangeEvent<HTMLInputElement>;
|
|
353
|
+
_onBlurRef.current?.(syntheticEvent as any);
|
|
354
|
+
};
|
|
355
|
+
}, [onBlurRef, focused]);
|
|
356
|
+
|
|
357
|
+
const renderFunc = render || DefaultComponent;
|
|
358
|
+
const showPlaceholder = !hasLabel || focused || !!value;
|
|
359
|
+
const placeholderText = showPlaceholder ? placeholder : undefined;
|
|
360
|
+
|
|
361
|
+
const labelHeight = labelLayout.height;
|
|
362
|
+
const finalHeight = +labelHeight;
|
|
363
|
+
const inputHeight = finalHeight < inputMinHeight ? inputMinHeight : finalHeight;
|
|
364
|
+
|
|
365
|
+
const computedStyles = useMemo(
|
|
366
|
+
() => ({
|
|
367
|
+
textInputStyle: [
|
|
368
|
+
styles.input,
|
|
369
|
+
!multiline ? { height: inputHeight || labelHeight } : {},
|
|
370
|
+
multiline && variant === 'outlined' && { paddingTop: 12 },
|
|
371
|
+
{
|
|
372
|
+
textAlignVertical: multiline ? ('top' as const) : ('center' as const),
|
|
373
|
+
textAlign: I18nManager.isRTL ? ('right' as const) : ('left' as const),
|
|
374
|
+
},
|
|
375
|
+
Platform.OS === 'ios' && !multiline
|
|
376
|
+
? { lineHeight: undefined, textAlign: undefined }
|
|
377
|
+
: {},
|
|
378
|
+
inputStyle,
|
|
379
|
+
],
|
|
380
|
+
stateLayerStyle: [styles.stateLayer, stateLayerProps?.style],
|
|
381
|
+
}),
|
|
382
|
+
// forcing useMemo to recompute when state, size or variant change
|
|
383
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
384
|
+
[
|
|
385
|
+
size,
|
|
263
386
|
state,
|
|
387
|
+
variant,
|
|
388
|
+
multiline,
|
|
389
|
+
inputHeight,
|
|
390
|
+
labelHeight,
|
|
391
|
+
inputStyle,
|
|
392
|
+
stateLayerProps?.style,
|
|
393
|
+
],
|
|
394
|
+
);
|
|
395
|
+
const hasValue = !!value;
|
|
396
|
+
|
|
397
|
+
const contextValue = useMemo(
|
|
398
|
+
() => ({
|
|
399
|
+
variant,
|
|
264
400
|
size,
|
|
265
|
-
|
|
401
|
+
state,
|
|
402
|
+
disabled,
|
|
403
|
+
hasValue,
|
|
404
|
+
error: errorProp,
|
|
405
|
+
focused,
|
|
406
|
+
hovered,
|
|
407
|
+
hasLabel,
|
|
408
|
+
required,
|
|
409
|
+
multiline,
|
|
410
|
+
labelLayout,
|
|
411
|
+
leftElementLayout,
|
|
412
|
+
onLayoutLabel: handleLayoutAnimatedText,
|
|
413
|
+
onLayoutLeftElement: handleLayoutLeftElement,
|
|
414
|
+
forceFocus,
|
|
415
|
+
}),
|
|
416
|
+
[
|
|
417
|
+
variant,
|
|
418
|
+
size,
|
|
419
|
+
state,
|
|
420
|
+
disabled,
|
|
421
|
+
errorProp,
|
|
422
|
+
focused,
|
|
423
|
+
hovered,
|
|
424
|
+
hasValue,
|
|
425
|
+
hasLabel,
|
|
426
|
+
required,
|
|
427
|
+
multiline,
|
|
428
|
+
labelLayout,
|
|
429
|
+
leftElementLayout,
|
|
430
|
+
handleLayoutAnimatedText,
|
|
431
|
+
handleLayoutLeftElement,
|
|
432
|
+
forceFocus,
|
|
433
|
+
],
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const outlineElement = TextInput_Outline.length > 0 ? TextInput_Outline : <TextInputOutline />;
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<TextInputContext value={contextValue}>
|
|
440
|
+
<View
|
|
441
|
+
ref={actionsRef}
|
|
442
|
+
{...containerProps}
|
|
443
|
+
style={[styles.root, style, containerProps?.style]}>
|
|
444
|
+
{outlineElement}
|
|
445
|
+
{variant === 'flat' && (
|
|
446
|
+
<StateLayer
|
|
447
|
+
testID={testID && `${testID}--stateLayer`}
|
|
448
|
+
{...stateLayerProps}
|
|
449
|
+
style={computedStyles.stateLayerStyle}
|
|
450
|
+
/>
|
|
451
|
+
)}
|
|
266
452
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
453
|
+
{TextInput_Left}
|
|
454
|
+
<View
|
|
455
|
+
{...inputWrapperProps}
|
|
456
|
+
style={[
|
|
457
|
+
styles.inputWrapper,
|
|
458
|
+
{
|
|
459
|
+
minHeight: labelHeight,
|
|
460
|
+
},
|
|
461
|
+
inputWrapperProps?.style,
|
|
462
|
+
]}>
|
|
463
|
+
{TextInput_Label}
|
|
464
|
+
{renderFunc({
|
|
465
|
+
placeholder: placeholderText,
|
|
466
|
+
ref: inputRefLocal,
|
|
467
|
+
placeholderTextColor: placeholderTextColor,
|
|
468
|
+
selectionColor: selectionColor,
|
|
469
|
+
editable: !disabled && editable,
|
|
470
|
+
underlineColorAndroid: 'transparent' as const,
|
|
471
|
+
multiline,
|
|
472
|
+
size,
|
|
473
|
+
onFocus: handleFocus,
|
|
474
|
+
onBlur: handleBlur,
|
|
475
|
+
onChangeText: onChangeValue,
|
|
476
|
+
value: value,
|
|
477
|
+
...rest,
|
|
478
|
+
style: computedStyles.textInputStyle,
|
|
479
|
+
})}
|
|
480
|
+
</View>
|
|
481
|
+
{TextInput_Right}
|
|
482
|
+
</View>
|
|
483
|
+
{TextInput_SupportingText}
|
|
484
|
+
{restChildren}
|
|
485
|
+
</TextInputContext>
|
|
486
|
+
);
|
|
487
|
+
};
|
|
276
488
|
|
|
277
|
-
|
|
278
|
-
measured: boolean;
|
|
279
|
-
width: number;
|
|
280
|
-
height: number;
|
|
281
|
-
}>({
|
|
282
|
-
measured: false,
|
|
283
|
-
width: 0,
|
|
284
|
-
height: 0,
|
|
285
|
-
});
|
|
489
|
+
export default memo(TextInput);
|
|
286
490
|
|
|
287
|
-
|
|
288
|
-
|
|
491
|
+
/**
|
|
492
|
+
* TextInput.Label - Renders the animated floating label
|
|
493
|
+
*/
|
|
494
|
+
export const TextInputLabel = memo(
|
|
495
|
+
({
|
|
496
|
+
children,
|
|
497
|
+
style,
|
|
498
|
+
floatingStyle,
|
|
499
|
+
maxFontSizeMultiplier = 15,
|
|
500
|
+
}: TextInputLabelCompoundProps) => {
|
|
501
|
+
const {
|
|
502
|
+
labelLayout,
|
|
503
|
+
leftElementLayout,
|
|
504
|
+
hasValue,
|
|
505
|
+
focused,
|
|
506
|
+
error,
|
|
507
|
+
required,
|
|
508
|
+
onLayoutLabel,
|
|
509
|
+
variant,
|
|
510
|
+
size,
|
|
511
|
+
state,
|
|
512
|
+
} = useContext(TextInputContext);
|
|
289
513
|
|
|
290
|
-
|
|
514
|
+
textInputLabelStyles.useVariants({
|
|
515
|
+
variant: variant as any,
|
|
516
|
+
state: state as any,
|
|
517
|
+
size,
|
|
518
|
+
});
|
|
291
519
|
|
|
292
|
-
const {
|
|
293
|
-
|
|
520
|
+
const { current: labelAnimation } = useRef<Animated.Value>(
|
|
521
|
+
new Animated.Value(hasValue ? 0 : 1),
|
|
522
|
+
);
|
|
523
|
+
const { current: errorAnimation } = useRef<Animated.Value>(
|
|
524
|
+
new Animated.Value(error ? 1 : 0),
|
|
525
|
+
);
|
|
294
526
|
|
|
295
527
|
useEffect(() => {
|
|
296
|
-
|
|
297
|
-
if (errorProp) {
|
|
298
|
-
// show error
|
|
528
|
+
if (error) {
|
|
299
529
|
Animated.timing(errorAnimation, {
|
|
300
530
|
toValue: 1,
|
|
301
531
|
duration: FOCUS_ANIMATION_DURATION * animationScale,
|
|
302
|
-
// To prevent this - https://github.com/callstack/react-native-paper/issues/941
|
|
303
532
|
useNativeDriver: true,
|
|
304
533
|
}).start();
|
|
305
|
-
|
|
306
534
|
return;
|
|
307
535
|
}
|
|
308
536
|
|
|
309
|
-
// hide error
|
|
310
537
|
Animated.timing(errorAnimation, {
|
|
311
538
|
toValue: 0,
|
|
312
539
|
duration: BLUR_ANIMATION_DURATION * animationScale,
|
|
313
|
-
// To prevent this - https://github.com/callstack/react-native-paper/issues/941
|
|
314
540
|
useNativeDriver: true,
|
|
315
541
|
}).start();
|
|
316
|
-
}, [
|
|
542
|
+
}, [error, errorAnimation]);
|
|
317
543
|
|
|
318
|
-
|
|
319
|
-
// Show placeholder text only if the input is focused, or there's no label
|
|
320
|
-
// We don't show placeholder if there's a label because the label acts as placeholder
|
|
321
|
-
// When focused, the label moves up, so we can show a placeholder
|
|
322
|
-
if (focused || !rest.label) {
|
|
323
|
-
// Set the placeholder in a delay to offset the label animation
|
|
324
|
-
// If we show it immediately, they'll overlap and look ugly
|
|
325
|
-
timer.current = setTimeout(
|
|
326
|
-
() => setPlaceholder(rest.placeholder),
|
|
327
|
-
50,
|
|
328
|
-
) as unknown as NodeJS.Timeout;
|
|
329
|
-
} else {
|
|
330
|
-
// hidePlaceholder
|
|
331
|
-
setPlaceholder('');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return () => {
|
|
335
|
-
if (timer.current) {
|
|
336
|
-
clearTimeout(timer.current);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
}, [focused, rest.label, rest.placeholder]);
|
|
340
|
-
|
|
341
|
-
const hasValue = !!value || focused;
|
|
544
|
+
const shouldMinimize = hasValue || focused;
|
|
342
545
|
|
|
343
546
|
useEffect(() => {
|
|
344
|
-
|
|
345
|
-
// In minimized mode, the label moves up and becomes small
|
|
346
|
-
// workaround for animated regression for react native > 0.61
|
|
347
|
-
// https://github.com/callstack/react-native-paper/pull/1440
|
|
348
|
-
if (hasValue) {
|
|
349
|
-
// minimize label
|
|
547
|
+
if (shouldMinimize) {
|
|
350
548
|
Animated.timing(labelAnimation, {
|
|
351
549
|
toValue: 0,
|
|
352
550
|
duration: BLUR_ANIMATION_DURATION * animationScale,
|
|
353
|
-
// To prevent this - https://github.com/callstack/react-native-paper/issues/941
|
|
354
551
|
useNativeDriver: true,
|
|
355
552
|
}).start();
|
|
356
553
|
} else {
|
|
357
|
-
// restore label
|
|
358
554
|
Animated.timing(labelAnimation, {
|
|
359
555
|
toValue: 1,
|
|
360
556
|
duration: FOCUS_ANIMATION_DURATION * animationScale,
|
|
361
|
-
// To prevent this - https://github.com/callstack/react-native-paper/issues/941
|
|
362
557
|
useNativeDriver: true,
|
|
363
558
|
}).start();
|
|
364
559
|
}
|
|
365
|
-
}, [focused,
|
|
560
|
+
}, [focused, shouldMinimize, labelAnimation]);
|
|
366
561
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
562
|
+
if (variant === 'plain') {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const labelScale = minimizedLabelFontSize / maximizedLabelFontSize;
|
|
567
|
+
const floatingLabelVerticalOffset = variant === 'flat' ? 16 : 0;
|
|
568
|
+
const labelWidth = labelLayout.width;
|
|
569
|
+
const labelHalfWidth = labelWidth / 2;
|
|
570
|
+
const baseLabelTranslateX =
|
|
571
|
+
(I18nManager.isRTL ? 1 : -1) *
|
|
572
|
+
(labelScale - 1 + labelHalfWidth - (labelScale * labelWidth) / 2);
|
|
573
|
+
const resolvedBaseLabelTranslateX =
|
|
574
|
+
variant === 'outlined'
|
|
575
|
+
? baseLabelTranslateX - leftElementLayout.width
|
|
576
|
+
: baseLabelTranslateX;
|
|
372
577
|
|
|
373
|
-
|
|
578
|
+
return (
|
|
579
|
+
<InputLabel
|
|
580
|
+
hasValue={hasValue}
|
|
581
|
+
focused={focused}
|
|
582
|
+
labelAnimation={labelAnimation}
|
|
583
|
+
errorAnimation={errorAnimation}
|
|
584
|
+
labelLayout={labelLayout}
|
|
585
|
+
label={children}
|
|
586
|
+
floatingStyle={[textInputLabelStyles.floatingLabel, floatingStyle]}
|
|
587
|
+
floatingLabelVerticalOffset={floatingLabelVerticalOffset}
|
|
588
|
+
required={required}
|
|
589
|
+
onLayoutAnimatedText={onLayoutLabel}
|
|
590
|
+
error={error}
|
|
591
|
+
baseLabelTranslateX={resolvedBaseLabelTranslateX}
|
|
592
|
+
labelScale={labelScale}
|
|
593
|
+
wiggleOffsetX={labelWiggleXOffset}
|
|
594
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
595
|
+
style={[textInputLabelStyles.labelText, style]}
|
|
596
|
+
/>
|
|
597
|
+
);
|
|
598
|
+
},
|
|
599
|
+
);
|
|
374
600
|
|
|
375
|
-
|
|
601
|
+
TextInputLabel.displayName = 'TextInput_Label';
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* TextInput.Left - Container for left-positioned elements
|
|
605
|
+
*/
|
|
606
|
+
export const TextInputLeft = memo(
|
|
607
|
+
({
|
|
608
|
+
children,
|
|
609
|
+
style,
|
|
610
|
+
onLayout,
|
|
611
|
+
onPress: onPressProp,
|
|
612
|
+
...rest
|
|
613
|
+
}: TextInputElementCompoundProps) => {
|
|
614
|
+
const { forceFocus, onLayoutLeftElement, state } = useContext(TextInputContext);
|
|
615
|
+
|
|
616
|
+
textInputLeftStyles.useVariants({
|
|
617
|
+
state: state as any,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const handleLayout = useCallback(
|
|
621
|
+
(e: LayoutChangeEvent) => {
|
|
622
|
+
onLayoutLeftElement(e);
|
|
623
|
+
onLayout?.(e);
|
|
376
624
|
},
|
|
377
|
-
[
|
|
625
|
+
[onLayoutLeftElement, onLayout],
|
|
378
626
|
);
|
|
379
627
|
|
|
380
|
-
const
|
|
381
|
-
(
|
|
382
|
-
if (
|
|
628
|
+
const onPress = useCallback(
|
|
629
|
+
(e: GestureResponderEvent) => {
|
|
630
|
+
if (onPressProp) {
|
|
631
|
+
onPressProp(e, forceFocus);
|
|
632
|
+
|
|
383
633
|
return;
|
|
384
634
|
}
|
|
385
635
|
|
|
386
|
-
|
|
387
|
-
onBlur?.(args);
|
|
636
|
+
forceFocus();
|
|
388
637
|
},
|
|
389
|
-
[
|
|
638
|
+
[forceFocus, onPressProp],
|
|
390
639
|
);
|
|
391
640
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
641
|
+
return (
|
|
642
|
+
<Pressable
|
|
643
|
+
onPress={onPress}
|
|
644
|
+
style={[textInputLeftStyles.leftElement, style]}
|
|
645
|
+
onLayout={handleLayout}
|
|
646
|
+
accessibilityRole="none"
|
|
647
|
+
tabIndex={-1}
|
|
648
|
+
{...rest}>
|
|
649
|
+
{children}
|
|
650
|
+
</Pressable>
|
|
651
|
+
);
|
|
652
|
+
},
|
|
653
|
+
);
|
|
399
654
|
|
|
400
|
-
|
|
401
|
-
setElementLayout({
|
|
402
|
-
width: e.nativeEvent.layout.width,
|
|
403
|
-
height: e.nativeEvent.layout.height,
|
|
404
|
-
measured: true,
|
|
405
|
-
});
|
|
406
|
-
}, []);
|
|
655
|
+
TextInputLeft.displayName = 'TextInput_Left';
|
|
407
656
|
|
|
408
|
-
|
|
657
|
+
/**
|
|
658
|
+
* TextInput.Right - Container for right-positioned elements
|
|
659
|
+
*/
|
|
660
|
+
export const TextInputRight = memo(
|
|
661
|
+
({ children, style, onPress: onPressProp, ...rest }: TextInputElementCompoundProps) => {
|
|
662
|
+
const { forceFocus, state } = useContext(TextInputContext);
|
|
409
663
|
|
|
410
|
-
|
|
664
|
+
textInputRightStyles.useVariants({
|
|
665
|
+
state: state as any,
|
|
666
|
+
});
|
|
411
667
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const _onBlurRef = onBlurRef;
|
|
418
|
-
const input = inputRefLocal.current;
|
|
419
|
-
|
|
420
|
-
return () => {
|
|
421
|
-
if (!isVersion18 || !input?.isFocused() || Platform.OS !== 'web') return;
|
|
422
|
-
|
|
423
|
-
const event = new Event('blur', { bubbles: true });
|
|
424
|
-
Object.defineProperty(event, 'target', {
|
|
425
|
-
writable: false,
|
|
426
|
-
value: input,
|
|
427
|
-
});
|
|
428
|
-
const syntheticEvent = createSyntheticEvent(
|
|
429
|
-
event,
|
|
430
|
-
) as React.ChangeEvent<HTMLInputElement>;
|
|
431
|
-
_onBlurRef.current?.(syntheticEvent as any);
|
|
432
|
-
};
|
|
433
|
-
}, [onBlurRef]);
|
|
434
|
-
|
|
435
|
-
// @ts-ignore // TODO - fix this
|
|
436
|
-
const componentStyles = styles.root;
|
|
668
|
+
const onPress = useCallback(
|
|
669
|
+
(e: GestureResponderEvent) => {
|
|
670
|
+
if (onPressProp) {
|
|
671
|
+
onPressProp(e, forceFocus);
|
|
437
672
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const labelHalfWidth = labelWidth / 2;
|
|
441
|
-
const labelScale =
|
|
442
|
-
minimizedLabelFontSize / (componentStyles.fontSize || maximizedLabelFontSize);
|
|
443
|
-
const baseLabelTranslateX =
|
|
444
|
-
(I18nManager.isRTL ? 1 : -1) *
|
|
445
|
-
(labelScale - 1 + labelHalfWidth - (labelScale * labelWidth) / 2);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
446
675
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
// );
|
|
451
|
-
|
|
452
|
-
const baseLabelTranslateXOutline =
|
|
453
|
-
baseLabelTranslateX - leftElementLayout.width - (left ? 0 : 0);
|
|
454
|
-
|
|
455
|
-
const backgroundColor =
|
|
456
|
-
styles.container?.backgroundColor || componentStyles.backgroundColor;
|
|
457
|
-
// const viableRadiuses = normalizeBorderRadiuses(componentStyles);
|
|
458
|
-
const finalHeight =
|
|
459
|
-
(+(componentStyles.height ?? 0) > 0 ? componentStyles.height : +labelHeight) ?? 0;
|
|
460
|
-
const inputHeight = finalHeight < inputMinHeight ? inputMinHeight : finalHeight;
|
|
461
|
-
const hasLabel = !!rest.label;
|
|
462
|
-
|
|
463
|
-
const computedStyles = useMemo(
|
|
464
|
-
() => ({
|
|
465
|
-
activeIndicator: styles.activeIndicator,
|
|
466
|
-
fontSize: componentStyles.fontSize,
|
|
467
|
-
fontWeight: componentStyles.fontWeight,
|
|
468
|
-
height: componentStyles.height,
|
|
469
|
-
textAlign: componentStyles.textAlign,
|
|
470
|
-
backgroundColor,
|
|
471
|
-
activeColor: (styles as any).root.activeColor,
|
|
472
|
-
baseLabelTranslateX:
|
|
473
|
-
variant === 'outlined' ? baseLabelTranslateXOutline : baseLabelTranslateX,
|
|
474
|
-
labelScale,
|
|
475
|
-
selectionColor: selectionColorProp || (styles as any).root.activeColor,
|
|
476
|
-
underlineColor: underlineColorProp,
|
|
477
|
-
activeUnderlineColor: activeUnderlineColorProp,
|
|
478
|
-
outlineColor: outlineColorProp,
|
|
479
|
-
activeOutlineColor: activeOutlineColorProp,
|
|
480
|
-
placeholderTextColor: placeholderTextColorProp || styles.placeholder?.color,
|
|
481
|
-
floatingLabelVerticalOffset,
|
|
482
|
-
labelWiggleXOffset,
|
|
483
|
-
textInputStyle: [
|
|
484
|
-
styles.inputText,
|
|
485
|
-
variant === 'flat' && hasLabel && { paddingTop: 12 },
|
|
486
|
-
!multiline || (multiline && componentStyles.height)
|
|
487
|
-
? { height: inputHeight || labelHeight }
|
|
488
|
-
: {},
|
|
489
|
-
multiline && variant === 'outlined' && { paddingTop: 12 },
|
|
490
|
-
{
|
|
491
|
-
textAlignVertical: multiline ? 'top' : 'center',
|
|
492
|
-
textAlign: componentStyles.textAlign
|
|
493
|
-
? componentStyles.textAlign
|
|
494
|
-
: I18nManager.isRTL
|
|
495
|
-
? 'right'
|
|
496
|
-
: 'left',
|
|
497
|
-
},
|
|
498
|
-
Platform.OS === 'ios' && !multiline
|
|
499
|
-
? { lineHeight: undefined, textAlign: undefined }
|
|
500
|
-
: {},
|
|
501
|
-
Platform.OS === 'web' && { outline: 'none' },
|
|
502
|
-
inputStyle,
|
|
503
|
-
],
|
|
504
|
-
inputContainerStyle: [
|
|
505
|
-
styles.labelContainer,
|
|
506
|
-
{
|
|
507
|
-
minHeight: componentStyles.height || labelHeight,
|
|
508
|
-
},
|
|
509
|
-
inputContainerStyle,
|
|
510
|
-
],
|
|
511
|
-
underlineStyle: [
|
|
512
|
-
styles.underline,
|
|
513
|
-
styles.activeIndicator,
|
|
514
|
-
hasActiveOutline && activeOutlineColorProp
|
|
515
|
-
? {
|
|
516
|
-
backgroundColor: hasActiveOutline
|
|
517
|
-
? activeUnderlineColorProp
|
|
518
|
-
: underlineColorProp,
|
|
519
|
-
}
|
|
520
|
-
: {},
|
|
521
|
-
],
|
|
522
|
-
outlineStyle: [
|
|
523
|
-
styles.outline,
|
|
524
|
-
hasActiveOutline && activeOutlineColorProp
|
|
525
|
-
? {
|
|
526
|
-
borderColor: hasActiveOutline
|
|
527
|
-
? activeOutlineColorProp
|
|
528
|
-
: outlineColorProp,
|
|
529
|
-
}
|
|
530
|
-
: {},
|
|
531
|
-
// viableRadiuses,
|
|
532
|
-
{},
|
|
533
|
-
],
|
|
534
|
-
patchContainer: [
|
|
535
|
-
StyleSheet.absoluteFill,
|
|
536
|
-
{
|
|
537
|
-
backgroundColor,
|
|
538
|
-
},
|
|
539
|
-
styles.patchContainer,
|
|
540
|
-
],
|
|
541
|
-
stateLayerStyle: [styles.stateLayer, stateLayerProps?.style],
|
|
542
|
-
}),
|
|
543
|
-
// forcing useMemo to recompute when state, size or variant change
|
|
544
|
-
// eslint-disable-next-line
|
|
545
|
-
[
|
|
546
|
-
hasLabel,
|
|
547
|
-
state,
|
|
548
|
-
size,
|
|
549
|
-
componentStyles,
|
|
550
|
-
backgroundColor,
|
|
551
|
-
variant,
|
|
552
|
-
parentBackground,
|
|
553
|
-
baseLabelTranslateXOutline,
|
|
554
|
-
baseLabelTranslateX,
|
|
555
|
-
labelScale,
|
|
556
|
-
selectionColorProp,
|
|
557
|
-
underlineColorProp,
|
|
558
|
-
activeUnderlineColorProp,
|
|
559
|
-
outlineColorProp,
|
|
560
|
-
activeOutlineColorProp,
|
|
561
|
-
placeholderTextColorProp,
|
|
562
|
-
floatingLabelVerticalOffset,
|
|
563
|
-
multiline,
|
|
564
|
-
inputHeight,
|
|
565
|
-
labelHeight,
|
|
566
|
-
inputStyle,
|
|
567
|
-
inputContainerStyle,
|
|
568
|
-
hasActiveOutline,
|
|
569
|
-
stateLayerProps?.style,
|
|
570
|
-
],
|
|
676
|
+
forceFocus();
|
|
677
|
+
},
|
|
678
|
+
[forceFocus, onPressProp],
|
|
571
679
|
);
|
|
572
680
|
|
|
573
681
|
return (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
{...stateLayerProps}
|
|
586
|
-
style={computedStyles.stateLayerStyle}
|
|
587
|
-
/>
|
|
588
|
-
</>
|
|
589
|
-
)}
|
|
590
|
-
{variant === 'outlined' && (
|
|
591
|
-
<Animated.View
|
|
592
|
-
testID="text-input-outline"
|
|
593
|
-
pointerEvents="none"
|
|
594
|
-
style={computedStyles.outlineStyle}
|
|
595
|
-
/>
|
|
596
|
-
)}
|
|
597
|
-
|
|
598
|
-
{left && (
|
|
599
|
-
<View
|
|
600
|
-
style={[styles.leftElement, leftElementStyle]}
|
|
601
|
-
onLayout={handleLayoutLeftElement}
|
|
602
|
-
testID={rest.testID && `${rest.testID}--text-input-left-element`}>
|
|
603
|
-
{typeof left === 'function'
|
|
604
|
-
? left?.({ color: computedStyles.activeColor, forceFocus, focused })
|
|
605
|
-
: left}
|
|
606
|
-
</View>
|
|
607
|
-
)}
|
|
608
|
-
|
|
609
|
-
<View
|
|
610
|
-
style={computedStyles.inputContainerStyle}
|
|
611
|
-
testID={rest.testID && `${rest.testID}-${variant}`}>
|
|
612
|
-
{Platform.OS !== 'android' &&
|
|
613
|
-
multiline &&
|
|
614
|
-
!!rest.label &&
|
|
615
|
-
variant === 'flat' && (
|
|
616
|
-
// Workaround for: https://github.com/callstack/react-native-paper/issues/2799
|
|
617
|
-
// Patch for a multiline TextInput with fixed height, which allow to avoid covering input label with its value.
|
|
618
|
-
<View
|
|
619
|
-
testID={rest.testID && `${rest.testID}--patch-container`}
|
|
620
|
-
pointerEvents="none"
|
|
621
|
-
style={computedStyles.patchContainer}
|
|
622
|
-
/>
|
|
623
|
-
)}
|
|
624
|
-
|
|
625
|
-
{variant !== 'plain' && (
|
|
626
|
-
<InputLabel
|
|
627
|
-
hasValue={!!value}
|
|
628
|
-
focused={focused}
|
|
629
|
-
labelAnimation={labelAnimation}
|
|
630
|
-
errorAnimation={errorAnimation}
|
|
631
|
-
labelLayout={labelLayout}
|
|
632
|
-
label={rest.label}
|
|
633
|
-
floatingStyle={styles.floatingLabel}
|
|
634
|
-
floatingLabelVerticalOffset={
|
|
635
|
-
computedStyles.floatingLabelVerticalOffset
|
|
636
|
-
}
|
|
637
|
-
required={required}
|
|
638
|
-
onLayoutAnimatedText={handleLayoutAnimatedText}
|
|
639
|
-
error={errorProp}
|
|
640
|
-
baseLabelTranslateX={computedStyles.baseLabelTranslateX}
|
|
641
|
-
labelScale={computedStyles.labelScale}
|
|
642
|
-
wiggleOffsetX={computedStyles.labelWiggleXOffset}
|
|
643
|
-
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
644
|
-
testID={rest.testID}
|
|
645
|
-
style={styles.labelText}
|
|
646
|
-
/>
|
|
647
|
-
)}
|
|
648
|
-
|
|
649
|
-
{render({
|
|
650
|
-
testID: rest.testID,
|
|
651
|
-
...rest,
|
|
652
|
-
style: computedStyles.textInputStyle,
|
|
653
|
-
ref: inputRefLocal,
|
|
654
|
-
onChangeText: onChangeValue,
|
|
655
|
-
placeholder: rest.label ? placeholder : rest.placeholder,
|
|
656
|
-
placeholderTextColor: computedStyles.placeholderTextColor,
|
|
657
|
-
editable: !disabled && editable,
|
|
658
|
-
selectionColor: computedStyles.selectionColor,
|
|
659
|
-
onFocus: handleFocus,
|
|
660
|
-
onBlur: handleBlur,
|
|
661
|
-
underlineColorAndroid: 'transparent',
|
|
662
|
-
multiline,
|
|
663
|
-
size,
|
|
664
|
-
})}
|
|
665
|
-
</View>
|
|
666
|
-
|
|
667
|
-
{right && (
|
|
668
|
-
<View
|
|
669
|
-
style={[styles.rightElement, rightElementStyle]}
|
|
670
|
-
testID={rest.testID && `${rest.testID}--text-input-right-element`}>
|
|
671
|
-
{typeof right === 'function'
|
|
672
|
-
? right?.({
|
|
673
|
-
color: computedStyles.activeColor,
|
|
674
|
-
forceFocus,
|
|
675
|
-
focused,
|
|
676
|
-
})
|
|
677
|
-
: right}
|
|
678
|
-
</View>
|
|
679
|
-
)}
|
|
680
|
-
</View>
|
|
682
|
+
<Pressable
|
|
683
|
+
onPress={onPress}
|
|
684
|
+
style={[textInputRightStyles.rightElement, style]}
|
|
685
|
+
accessibilityRole="none"
|
|
686
|
+
tabIndex={-1}
|
|
687
|
+
{...rest}>
|
|
688
|
+
{children}
|
|
689
|
+
</Pressable>
|
|
690
|
+
);
|
|
691
|
+
},
|
|
692
|
+
);
|
|
681
693
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
694
|
+
TextInputRight.displayName = 'TextInput_Right';
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* TextInput.Icon - Convenience component for icons within TextInput
|
|
698
|
+
*/
|
|
699
|
+
export const TextInputIcon = memo(
|
|
700
|
+
({ size: sizeProp, color: colorProp, style, ...rest }: TextInputIconCompoundProps) => {
|
|
701
|
+
const { state } = useContext(TextInputContext);
|
|
702
|
+
|
|
703
|
+
textInputIconStyles.useVariants({
|
|
704
|
+
// @ts-ignore - state includes 'default' which is valid but not in style variants
|
|
705
|
+
state,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const colorResolved = colorProp;
|
|
709
|
+
|
|
710
|
+
return (
|
|
711
|
+
<Icon
|
|
712
|
+
size={sizeProp ?? 20}
|
|
713
|
+
color={colorResolved}
|
|
714
|
+
style={[textInputIconStyles.root, style]}
|
|
715
|
+
{...rest}
|
|
716
|
+
/>
|
|
690
717
|
);
|
|
691
718
|
},
|
|
692
719
|
);
|
|
693
720
|
|
|
694
|
-
|
|
721
|
+
TextInputIcon.displayName = 'TextInput_Icon';
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* TextInput.SupportingText - Helper/error text below the input
|
|
725
|
+
*/
|
|
726
|
+
export const TextInputSupportingText = memo(
|
|
727
|
+
({ children, style }: TextInputSupportingTextCompoundProps) => {
|
|
728
|
+
const { error, state } = useContext(TextInputContext);
|
|
729
|
+
|
|
730
|
+
textInputSupportingTextStyles.useVariants({
|
|
731
|
+
state: state as any,
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
return (
|
|
735
|
+
<HelperText
|
|
736
|
+
variant={error ? 'error' : 'info'}
|
|
737
|
+
style={[textInputSupportingTextStyles.supportingText, style]}>
|
|
738
|
+
{children}
|
|
739
|
+
</HelperText>
|
|
740
|
+
);
|
|
741
|
+
},
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
TextInputSupportingText.displayName = 'TextInput_SupportingText';
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* TextInput.Outline - Renders the border overlay for outlined/flat variants.
|
|
748
|
+
* Rendered automatically if not provided. Use this to customize border styles.
|
|
749
|
+
*/
|
|
750
|
+
export const TextInputOutline = memo(({ style }: TextInputOutlineCompoundProps) => {
|
|
751
|
+
const { variant, state } = useContext(TextInputContext);
|
|
752
|
+
|
|
753
|
+
textInputOutlineStyles.useVariants({
|
|
754
|
+
variant: variant as any,
|
|
755
|
+
state: state as any,
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
if (variant === 'plain') {
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (variant === 'flat') {
|
|
763
|
+
return (
|
|
764
|
+
<View
|
|
765
|
+
style={[
|
|
766
|
+
textInputOutlineStyles.underline,
|
|
767
|
+
textInputOutlineStyles.activeIndicator,
|
|
768
|
+
style,
|
|
769
|
+
]}
|
|
770
|
+
/>
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// outlined
|
|
775
|
+
return <View pointerEvents="none" style={[textInputOutlineStyles.outline, style]} />;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
TextInputOutline.displayName = 'TextInput_Outline';
|