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