@react-native-ama/forms 1.0.1-alpha.2 → 1.1.0
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/dist/components/Form.d.ts +4 -1
- package/dist/components/Form.js +32 -19
- package/dist/hooks/useFormField.d.ts +2 -1
- package/dist/hooks/useFormField.js +2 -2
- package/dist/hooks/useTextInput.d.ts +31 -21
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -2
- package/package.json +5 -6
- package/src/components/Form.tsx +88 -60
- package/src/components/FormSubmit.test.tsx +1 -1
- package/src/hooks/useFormField.ts +3 -1
- package/src/index.ts +7 -1
|
@@ -5,6 +5,7 @@ export type FormProps = React.PropsWithChildren<{
|
|
|
5
5
|
}>;
|
|
6
6
|
export type FormActions = {
|
|
7
7
|
focusFirstInvalidField: () => void;
|
|
8
|
+
focusFieldAt: (fieldNumber: number) => void;
|
|
8
9
|
};
|
|
9
10
|
export declare const Form: React.ForwardRefExoticComponent<Omit<FormProps, "ref"> & React.RefAttributes<FormActions>>;
|
|
10
11
|
export type FormContextValue = {
|
|
@@ -21,4 +22,6 @@ export type FormRef = {
|
|
|
21
22
|
isEditable?: boolean;
|
|
22
23
|
};
|
|
23
24
|
export declare const FormContext: React.Context<FormContextValue | null>;
|
|
24
|
-
export declare const useForm: (
|
|
25
|
+
export declare const useForm: ({ suppressError }?: {
|
|
26
|
+
suppressError?: boolean | undefined;
|
|
27
|
+
}) => FormContextValue;
|
package/dist/components/Form.js
CHANGED
|
@@ -41,7 +41,7 @@ exports.Form = React.forwardRef(({ children, onSubmit }, ref) => {
|
|
|
41
41
|
const refs = React.useRef([]);
|
|
42
42
|
const { setFocus } = (0, core_1.useFocus)();
|
|
43
43
|
const checks = __DEV__ ? core_1.useChecks === null || core_1.useChecks === void 0 ? void 0 : (0, core_1.useChecks)() : undefined;
|
|
44
|
-
const focusField = (nextField) => {
|
|
44
|
+
const focusField = React.useCallback((nextField) => {
|
|
45
45
|
var _a, _b, _c;
|
|
46
46
|
/**
|
|
47
47
|
* Refs passed as prop have another ".current"
|
|
@@ -80,38 +80,51 @@ exports.Form = React.forwardRef(({ children, onSubmit }, ref) => {
|
|
|
80
80
|
else if (nextRefElement === null || nextRefElement === void 0 ? void 0 : nextRefElement.current) {
|
|
81
81
|
setFocus(nextRefElement === null || nextRefElement === void 0 ? void 0 : nextRefElement.current);
|
|
82
82
|
}
|
|
83
|
-
};
|
|
84
|
-
const
|
|
85
|
-
const isValid = yield onSubmit();
|
|
86
|
-
if (isValid) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
focusFirstInvalidField();
|
|
90
|
-
});
|
|
91
|
-
const focusFirstInvalidField = () => {
|
|
83
|
+
}, [checks, setFocus]);
|
|
84
|
+
const focusFieldAt = React.useCallback((position) => {
|
|
92
85
|
react_native_1.InteractionManager.runAfterInteractions(() => {
|
|
93
86
|
setTimeout(() => {
|
|
94
|
-
const fieldWithError =
|
|
95
|
-
__DEV__ &&
|
|
96
|
-
fieldWithError == null &&
|
|
97
|
-
(checks === null || checks === void 0 ? void 0 : checks.logResult('Form', {
|
|
98
|
-
message: 'The form validation has failed, but no component with error was found',
|
|
99
|
-
rule: 'NO_UNDEFINED',
|
|
100
|
-
}));
|
|
87
|
+
const fieldWithError = refs.current[position];
|
|
101
88
|
focusField(fieldWithError);
|
|
102
89
|
}, 0);
|
|
103
90
|
});
|
|
104
|
-
};
|
|
91
|
+
}, [focusField]);
|
|
92
|
+
const focusFirstInvalidField = React.useCallback(() => {
|
|
93
|
+
const fieldWithError = (refs.current || []).findIndex(fieldRef => fieldRef.hasValidation && fieldRef.hasError);
|
|
94
|
+
__DEV__ &&
|
|
95
|
+
fieldWithError == null &&
|
|
96
|
+
(checks === null || checks === void 0 ? void 0 : checks.logResult('Form', {
|
|
97
|
+
message: 'The form validation has failed, but no component with error was found',
|
|
98
|
+
rule: 'NO_UNDEFINED',
|
|
99
|
+
}));
|
|
100
|
+
focusFieldAt(fieldWithError);
|
|
101
|
+
}, [focusFieldAt, checks]);
|
|
102
|
+
const submitForm = React.useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
const isValid = yield onSubmit();
|
|
104
|
+
if (isValid) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
focusFirstInvalidField();
|
|
108
|
+
}), [focusFirstInvalidField, onSubmit]);
|
|
105
109
|
React.useImperativeHandle(ref, () => ({
|
|
106
110
|
focusFirstInvalidField,
|
|
111
|
+
focusFieldAt,
|
|
107
112
|
}));
|
|
108
113
|
return (<exports.FormContext.Provider value={{ refs: refs.current, submitForm, focusField }}>
|
|
109
114
|
{children}
|
|
110
115
|
</exports.FormContext.Provider>);
|
|
111
116
|
});
|
|
112
117
|
exports.FormContext = React.createContext(null);
|
|
113
|
-
const
|
|
118
|
+
const DEFAULT_CONTEXT_VALUE = {
|
|
119
|
+
refs: [],
|
|
120
|
+
submitForm: () => Promise.resolve(),
|
|
121
|
+
focusField: () => { },
|
|
122
|
+
};
|
|
123
|
+
const useForm = ({ suppressError } = { suppressError: false }) => {
|
|
114
124
|
const context = React.useContext(exports.FormContext);
|
|
125
|
+
if (!context && suppressError) {
|
|
126
|
+
return DEFAULT_CONTEXT_VALUE; // return default values so internal useForm hooks don't throw undefined errors when user choose to suppress errors
|
|
127
|
+
}
|
|
115
128
|
if (!context) {
|
|
116
129
|
__DEV__ &&
|
|
117
130
|
console.error('Please wrap your form field inside the <Form /> component');
|
|
@@ -12,8 +12,9 @@ export type UseFormField = {
|
|
|
12
12
|
hasValidation: boolean;
|
|
13
13
|
hasError?: boolean;
|
|
14
14
|
errorMessage?: string;
|
|
15
|
+
suppressError?: boolean;
|
|
15
16
|
};
|
|
16
|
-
export declare const useFormField: ({ ref, hasFocusCallback, id, nextFieldId, nextFormFieldRef, hasValidation, hasError, accessibilityHint, errorMessage, editable, style, }: UseFormField) => {
|
|
17
|
+
export declare const useFormField: ({ ref, hasFocusCallback, id, nextFieldId, nextFormFieldRef, hasValidation, hasError, accessibilityHint, errorMessage, editable, suppressError, style, }: UseFormField) => {
|
|
17
18
|
focusNextFormField: () => void;
|
|
18
19
|
isLastField: () => boolean;
|
|
19
20
|
style: any;
|
|
@@ -9,8 +9,8 @@ const internal_1 = require("@react-native-ama/internal");
|
|
|
9
9
|
const react_1 = __importDefault(require("react"));
|
|
10
10
|
const react_native_1 = require("react-native");
|
|
11
11
|
const Form_1 = require("../components/Form");
|
|
12
|
-
const useFormField = ({ ref, hasFocusCallback, id, nextFieldId, nextFormFieldRef, hasValidation, hasError, accessibilityHint, errorMessage, editable = true, style = {}, }) => {
|
|
13
|
-
const { refs, submitForm, focusField } = (0, Form_1.useForm)();
|
|
12
|
+
const useFormField = ({ ref, hasFocusCallback, id, nextFieldId, nextFormFieldRef, hasValidation, hasError, accessibilityHint, errorMessage, editable = true, suppressError, style = {}, }) => {
|
|
13
|
+
const { refs, submitForm, focusField } = (0, Form_1.useForm)({ suppressError });
|
|
14
14
|
const fieldRef = react_1.default.useRef(ref);
|
|
15
15
|
const checks = __DEV__ ? core_1.useChecks === null || core_1.useChecks === void 0 ? void 0 : (0, core_1.useChecks)() : undefined;
|
|
16
16
|
const getMyIndex = () => {
|
|
@@ -18,7 +18,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
18
18
|
style: any;
|
|
19
19
|
allowFontScaling?: boolean | undefined;
|
|
20
20
|
autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined;
|
|
21
|
-
autoComplete?: "off" | "
|
|
21
|
+
autoComplete?: "off" | "additional-name" | "address-line1" | "address-line2" | "birthdate-day" | "birthdate-full" | "birthdate-month" | "birthdate-year" | "cc-csc" | "cc-exp" | "cc-exp-day" | "cc-exp-month" | "cc-exp-year" | "cc-number" | "cc-name" | "cc-given-name" | "cc-middle-name" | "cc-family-name" | "cc-type" | "country" | "current-password" | "email" | "family-name" | "gender" | "given-name" | "honorific-prefix" | "honorific-suffix" | "name" | "name-family" | "name-given" | "name-middle" | "name-middle-initial" | "name-prefix" | "name-suffix" | "new-password" | "nickname" | "one-time-code" | "organization" | "organization-title" | "password" | "password-new" | "postal-address" | "postal-address-country" | "postal-address-extended" | "postal-address-extended-postal-code" | "postal-address-locality" | "postal-address-region" | "postal-code" | "street-address" | "sms-otp" | "tel" | "tel-country-code" | "tel-national" | "tel-device" | "url" | "username" | "username-new" | undefined;
|
|
22
22
|
autoCorrect?: boolean | undefined;
|
|
23
23
|
autoFocus?: boolean | undefined;
|
|
24
24
|
blurOnSubmit?: boolean | undefined;
|
|
@@ -35,16 +35,17 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
35
35
|
onChangeText?: ((text: string) => void) | undefined;
|
|
36
36
|
onContentSizeChange?: ((e: NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
|
|
37
37
|
onEndEditing?: ((e: NativeSyntheticEvent<import("react-native").TextInputEndEditingEventData>) => void) | undefined;
|
|
38
|
+
onPress?: ((e: NativeSyntheticEvent<import("react-native").NativeTouchEvent>) => void) | undefined;
|
|
38
39
|
onPressIn?: ((e: NativeSyntheticEvent<import("react-native").NativeTouchEvent>) => void) | undefined;
|
|
39
40
|
onPressOut?: ((e: NativeSyntheticEvent<import("react-native").NativeTouchEvent>) => void) | undefined;
|
|
40
41
|
onFocus?: ((e: NativeSyntheticEvent<import("react-native").TextInputFocusEventData>) => void) | undefined;
|
|
41
42
|
onSelectionChange?: ((e: NativeSyntheticEvent<import("react-native").TextInputSelectionChangeEventData>) => void) | undefined;
|
|
42
43
|
onSubmitEditing?: ((e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => void) | undefined;
|
|
43
|
-
onTextInput?: ((e: NativeSyntheticEvent<import("react-native").TextInputTextInputEventData>) => void) | undefined;
|
|
44
44
|
onScroll?: ((e: NativeSyntheticEvent<import("react-native").TextInputScrollEventData>) => void) | undefined;
|
|
45
45
|
onKeyPress?: ((e: NativeSyntheticEvent<import("react-native").TextInputKeyPressEventData>) => void) | undefined;
|
|
46
46
|
placeholder?: string | undefined;
|
|
47
47
|
placeholderTextColor?: import("react-native").ColorValue | undefined;
|
|
48
|
+
readOnly?: boolean | undefined;
|
|
48
49
|
returnKeyType?: ReturnKeyTypeOptions | undefined;
|
|
49
50
|
enterKeyHint?: import("react-native").EnterKeyHintTypeOptions | undefined;
|
|
50
51
|
secureTextEntry?: boolean | undefined;
|
|
@@ -60,20 +61,21 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
60
61
|
value?: string | undefined;
|
|
61
62
|
maxFontSizeMultiplier?: number | null | undefined;
|
|
62
63
|
children?: React.ReactNode;
|
|
63
|
-
hitSlop?: import("react-native").Insets | undefined;
|
|
64
|
+
hitSlop?: number | import("react-native").Insets | null | undefined;
|
|
64
65
|
id?: string | undefined;
|
|
66
|
+
needsOffscreenAlphaCompositing?: boolean | undefined;
|
|
65
67
|
onLayout?: ((event: LayoutChangeEvent) => void) | undefined;
|
|
66
68
|
pointerEvents?: "box-none" | "none" | "box-only" | "auto" | undefined;
|
|
67
69
|
removeClippedSubviews?: boolean | undefined;
|
|
68
70
|
nativeID?: string | undefined;
|
|
69
71
|
collapsable?: boolean | undefined;
|
|
70
|
-
|
|
72
|
+
collapsableChildren?: boolean | undefined;
|
|
71
73
|
renderToHardwareTextureAndroid?: boolean | undefined;
|
|
72
74
|
focusable?: boolean | undefined;
|
|
75
|
+
tabIndex?: 0 | -1 | undefined;
|
|
73
76
|
shouldRasterizeIOS?: boolean | undefined;
|
|
74
77
|
isTVSelectable?: boolean | undefined;
|
|
75
78
|
hasTVPreferredFocus?: boolean | undefined;
|
|
76
|
-
tvParallaxProperties?: import("react-native").TVParallaxProperties | undefined;
|
|
77
79
|
tvParallaxShiftDistanceX?: number | undefined;
|
|
78
80
|
tvParallaxShiftDistanceY?: number | undefined;
|
|
79
81
|
tvParallaxTiltAngle?: number | undefined;
|
|
@@ -121,7 +123,6 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
121
123
|
'aria-disabled'?: boolean | undefined;
|
|
122
124
|
'aria-expanded'?: boolean | undefined;
|
|
123
125
|
'aria-selected'?: boolean | undefined;
|
|
124
|
-
'aria-labelledby'?: string | undefined;
|
|
125
126
|
accessibilityHint: string;
|
|
126
127
|
accessibilityValue?: import("react-native").AccessibilityValue | undefined;
|
|
127
128
|
'aria-valuemax'?: number | undefined;
|
|
@@ -131,11 +132,12 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
131
132
|
onAccessibilityAction?: ((event: import("react-native").AccessibilityActionEvent) => void) | undefined;
|
|
132
133
|
importantForAccessibility?: "auto" | "yes" | "no" | "no-hide-descendants" | undefined;
|
|
133
134
|
'aria-hidden'?: boolean | undefined;
|
|
134
|
-
'aria-live'?: "polite" | "assertive" | "off" | undefined;
|
|
135
135
|
'aria-modal'?: boolean | undefined;
|
|
136
136
|
role?: import("react-native").Role | undefined;
|
|
137
|
-
accessibilityLiveRegion?: "none" | "polite" | "assertive" | undefined;
|
|
138
137
|
accessibilityLabelledBy?: string | string[] | undefined;
|
|
138
|
+
'aria-labelledby'?: string | undefined;
|
|
139
|
+
accessibilityLiveRegion?: "none" | "polite" | "assertive" | undefined;
|
|
140
|
+
'aria-live'?: "polite" | "assertive" | "off" | undefined;
|
|
139
141
|
accessibilityElementsHidden?: boolean | undefined;
|
|
140
142
|
accessibilityViewIsModal?: boolean | undefined;
|
|
141
143
|
onAccessibilityEscape?: (() => void) | undefined;
|
|
@@ -152,10 +154,12 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
152
154
|
rejectResponderTermination?: boolean | null | undefined;
|
|
153
155
|
selectionState?: import("react-native").DocumentSelectionState | undefined;
|
|
154
156
|
spellCheck?: boolean | undefined;
|
|
155
|
-
textContentType?: "none" | "
|
|
157
|
+
textContentType?: "none" | "name" | "nickname" | "password" | "username" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "creditCardExpiration" | "creditCardExpirationMonth" | "creditCardExpirationYear" | "creditCardSecurityCode" | "creditCardType" | "creditCardName" | "creditCardGivenName" | "creditCardMiddleName" | "creditCardFamilyName" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "newPassword" | "oneTimeCode" | "birthdate" | "birthdateDay" | "birthdateMonth" | "birthdateYear" | undefined;
|
|
156
158
|
scrollEnabled?: boolean | undefined;
|
|
157
159
|
lineBreakStrategyIOS?: "none" | "standard" | "hangul-word" | "push-out" | undefined;
|
|
160
|
+
smartInsertDelete?: boolean | undefined;
|
|
158
161
|
cursorColor?: import("react-native").ColorValue | null | undefined;
|
|
162
|
+
selectionHandleColor?: import("react-native").ColorValue | null | undefined;
|
|
159
163
|
importantForAutofill?: "auto" | "yes" | "no" | "noExcludeDescendants" | "yesExcludeDescendants" | undefined;
|
|
160
164
|
disableFullscreenUI?: boolean | undefined;
|
|
161
165
|
inlineImageLeft?: string | undefined;
|
|
@@ -166,7 +170,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
166
170
|
underlineColorAndroid?: import("react-native").ColorValue | undefined;
|
|
167
171
|
textAlignVertical?: "center" | "auto" | "top" | "bottom" | undefined;
|
|
168
172
|
showSoftInputOnFocus?: boolean | undefined;
|
|
169
|
-
verticalAlign?: "auto" | "
|
|
173
|
+
verticalAlign?: "auto" | "top" | "bottom" | "middle" | undefined;
|
|
170
174
|
labelComponent?: JSX.Element | undefined;
|
|
171
175
|
labelPosition?: "beforeInput" | "afterInput" | undefined;
|
|
172
176
|
nextFormField?: React.RefObject<import("react-native").TextInput> | undefined;
|
|
@@ -179,11 +183,12 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
179
183
|
focusNextFormField: () => void;
|
|
180
184
|
isLastField: () => boolean;
|
|
181
185
|
ref?: React.RefObject<any> | React.ForwardedRef<any> | undefined;
|
|
186
|
+
suppressError?: boolean | undefined;
|
|
182
187
|
nextFormFieldRef?: React.RefObject<any> | undefined;
|
|
183
188
|
} | {
|
|
184
189
|
allowFontScaling?: boolean | undefined;
|
|
185
190
|
autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined;
|
|
186
|
-
autoComplete?: "off" | "
|
|
191
|
+
autoComplete?: "off" | "additional-name" | "address-line1" | "address-line2" | "birthdate-day" | "birthdate-full" | "birthdate-month" | "birthdate-year" | "cc-csc" | "cc-exp" | "cc-exp-day" | "cc-exp-month" | "cc-exp-year" | "cc-number" | "cc-name" | "cc-given-name" | "cc-middle-name" | "cc-family-name" | "cc-type" | "country" | "current-password" | "email" | "family-name" | "gender" | "given-name" | "honorific-prefix" | "honorific-suffix" | "name" | "name-family" | "name-given" | "name-middle" | "name-middle-initial" | "name-prefix" | "name-suffix" | "new-password" | "nickname" | "one-time-code" | "organization" | "organization-title" | "password" | "password-new" | "postal-address" | "postal-address-country" | "postal-address-extended" | "postal-address-extended-postal-code" | "postal-address-locality" | "postal-address-region" | "postal-code" | "street-address" | "sms-otp" | "tel" | "tel-country-code" | "tel-national" | "tel-device" | "url" | "username" | "username-new" | undefined;
|
|
187
192
|
autoCorrect?: boolean | undefined;
|
|
188
193
|
autoFocus?: boolean | undefined;
|
|
189
194
|
blurOnSubmit?: boolean | undefined;
|
|
@@ -200,16 +205,17 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
200
205
|
onChangeText?: ((text: string) => void) | undefined;
|
|
201
206
|
onContentSizeChange?: ((e: NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
|
|
202
207
|
onEndEditing?: ((e: NativeSyntheticEvent<import("react-native").TextInputEndEditingEventData>) => void) | undefined;
|
|
208
|
+
onPress?: ((e: NativeSyntheticEvent<import("react-native").NativeTouchEvent>) => void) | undefined;
|
|
203
209
|
onPressIn?: ((e: NativeSyntheticEvent<import("react-native").NativeTouchEvent>) => void) | undefined;
|
|
204
210
|
onPressOut?: ((e: NativeSyntheticEvent<import("react-native").NativeTouchEvent>) => void) | undefined;
|
|
205
211
|
onFocus?: ((e: NativeSyntheticEvent<import("react-native").TextInputFocusEventData>) => void) | undefined;
|
|
206
212
|
onSelectionChange?: ((e: NativeSyntheticEvent<import("react-native").TextInputSelectionChangeEventData>) => void) | undefined;
|
|
207
213
|
onSubmitEditing?: ((e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => void) | undefined;
|
|
208
|
-
onTextInput?: ((e: NativeSyntheticEvent<import("react-native").TextInputTextInputEventData>) => void) | undefined;
|
|
209
214
|
onScroll?: ((e: NativeSyntheticEvent<import("react-native").TextInputScrollEventData>) => void) | undefined;
|
|
210
215
|
onKeyPress?: ((e: NativeSyntheticEvent<import("react-native").TextInputKeyPressEventData>) => void) | undefined;
|
|
211
216
|
placeholder?: string | undefined;
|
|
212
217
|
placeholderTextColor?: import("react-native").ColorValue | undefined;
|
|
218
|
+
readOnly?: boolean | undefined;
|
|
213
219
|
returnKeyType?: ReturnKeyTypeOptions | undefined;
|
|
214
220
|
enterKeyHint?: import("react-native").EnterKeyHintTypeOptions | undefined;
|
|
215
221
|
secureTextEntry?: boolean | undefined;
|
|
@@ -219,27 +225,28 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
219
225
|
end?: number | undefined;
|
|
220
226
|
} | undefined;
|
|
221
227
|
selectionColor?: import("react-native").ColorValue | undefined;
|
|
222
|
-
style?: false | ViewStyle | import("react-native").RegisteredStyle<TextStyle> | import("react-native").RecursiveArray<import("react-native").Falsy | TextStyle | import("react-native").RegisteredStyle<TextStyle>> | null | undefined;
|
|
228
|
+
style?: false | "" | ViewStyle | import("react-native").RegisteredStyle<TextStyle> | import("react-native").RecursiveArray<import("react-native").Falsy | TextStyle | import("react-native").RegisteredStyle<TextStyle>> | null | undefined;
|
|
223
229
|
textAlign?: "center" | "left" | "right" | undefined;
|
|
224
230
|
testID?: string | undefined;
|
|
225
231
|
inputAccessoryViewID?: string | undefined;
|
|
226
232
|
value?: string | undefined;
|
|
227
233
|
maxFontSizeMultiplier?: number | null | undefined;
|
|
228
234
|
children?: React.ReactNode;
|
|
229
|
-
hitSlop?: import("react-native").Insets | undefined;
|
|
235
|
+
hitSlop?: number | import("react-native").Insets | null | undefined;
|
|
230
236
|
id?: string | undefined;
|
|
237
|
+
needsOffscreenAlphaCompositing?: boolean | undefined;
|
|
231
238
|
onLayout?: ((event: LayoutChangeEvent) => void) | undefined;
|
|
232
239
|
pointerEvents?: "box-none" | "none" | "box-only" | "auto" | undefined;
|
|
233
240
|
removeClippedSubviews?: boolean | undefined;
|
|
234
241
|
nativeID?: string | undefined;
|
|
235
242
|
collapsable?: boolean | undefined;
|
|
236
|
-
|
|
243
|
+
collapsableChildren?: boolean | undefined;
|
|
237
244
|
renderToHardwareTextureAndroid?: boolean | undefined;
|
|
238
245
|
focusable?: boolean | undefined;
|
|
246
|
+
tabIndex?: 0 | -1 | undefined;
|
|
239
247
|
shouldRasterizeIOS?: boolean | undefined;
|
|
240
248
|
isTVSelectable?: boolean | undefined;
|
|
241
249
|
hasTVPreferredFocus?: boolean | undefined;
|
|
242
|
-
tvParallaxProperties?: import("react-native").TVParallaxProperties | undefined;
|
|
243
250
|
tvParallaxShiftDistanceX?: number | undefined;
|
|
244
251
|
tvParallaxShiftDistanceY?: number | undefined;
|
|
245
252
|
tvParallaxTiltAngle?: number | undefined;
|
|
@@ -287,7 +294,6 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
287
294
|
'aria-disabled'?: boolean | undefined;
|
|
288
295
|
'aria-expanded'?: boolean | undefined;
|
|
289
296
|
'aria-selected'?: boolean | undefined;
|
|
290
|
-
'aria-labelledby'?: string | undefined;
|
|
291
297
|
accessibilityHint: string;
|
|
292
298
|
accessibilityValue?: import("react-native").AccessibilityValue | undefined;
|
|
293
299
|
'aria-valuemax'?: number | undefined;
|
|
@@ -297,11 +303,12 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
297
303
|
onAccessibilityAction?: ((event: import("react-native").AccessibilityActionEvent) => void) | undefined;
|
|
298
304
|
importantForAccessibility?: "auto" | "yes" | "no" | "no-hide-descendants" | undefined;
|
|
299
305
|
'aria-hidden'?: boolean | undefined;
|
|
300
|
-
'aria-live'?: "polite" | "assertive" | "off" | undefined;
|
|
301
306
|
'aria-modal'?: boolean | undefined;
|
|
302
307
|
role?: import("react-native").Role | undefined;
|
|
303
|
-
accessibilityLiveRegion?: "none" | "polite" | "assertive" | undefined;
|
|
304
308
|
accessibilityLabelledBy?: string | string[] | undefined;
|
|
309
|
+
'aria-labelledby'?: string | undefined;
|
|
310
|
+
accessibilityLiveRegion?: "none" | "polite" | "assertive" | undefined;
|
|
311
|
+
'aria-live'?: "polite" | "assertive" | "off" | undefined;
|
|
305
312
|
accessibilityElementsHidden?: boolean | undefined;
|
|
306
313
|
accessibilityViewIsModal?: boolean | undefined;
|
|
307
314
|
onAccessibilityEscape?: (() => void) | undefined;
|
|
@@ -318,10 +325,12 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
318
325
|
rejectResponderTermination?: boolean | null | undefined;
|
|
319
326
|
selectionState?: import("react-native").DocumentSelectionState | undefined;
|
|
320
327
|
spellCheck?: boolean | undefined;
|
|
321
|
-
textContentType?: "none" | "
|
|
328
|
+
textContentType?: "none" | "name" | "nickname" | "password" | "username" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "creditCardExpiration" | "creditCardExpirationMonth" | "creditCardExpirationYear" | "creditCardSecurityCode" | "creditCardType" | "creditCardName" | "creditCardGivenName" | "creditCardMiddleName" | "creditCardFamilyName" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "newPassword" | "oneTimeCode" | "birthdate" | "birthdateDay" | "birthdateMonth" | "birthdateYear" | undefined;
|
|
322
329
|
scrollEnabled?: boolean | undefined;
|
|
323
330
|
lineBreakStrategyIOS?: "none" | "standard" | "hangul-word" | "push-out" | undefined;
|
|
331
|
+
smartInsertDelete?: boolean | undefined;
|
|
324
332
|
cursorColor?: import("react-native").ColorValue | null | undefined;
|
|
333
|
+
selectionHandleColor?: import("react-native").ColorValue | null | undefined;
|
|
325
334
|
importantForAutofill?: "auto" | "yes" | "no" | "noExcludeDescendants" | "yesExcludeDescendants" | undefined;
|
|
326
335
|
disableFullscreenUI?: boolean | undefined;
|
|
327
336
|
inlineImageLeft?: string | undefined;
|
|
@@ -332,7 +341,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
332
341
|
underlineColorAndroid?: import("react-native").ColorValue | undefined;
|
|
333
342
|
textAlignVertical?: "center" | "auto" | "top" | "bottom" | undefined;
|
|
334
343
|
showSoftInputOnFocus?: boolean | undefined;
|
|
335
|
-
verticalAlign?: "auto" | "
|
|
344
|
+
verticalAlign?: "auto" | "top" | "bottom" | "middle" | undefined;
|
|
336
345
|
labelComponent?: JSX.Element | undefined;
|
|
337
346
|
labelPosition?: "beforeInput" | "afterInput" | undefined;
|
|
338
347
|
nextFormField?: React.RefObject<import("react-native").TextInput> | undefined;
|
|
@@ -345,6 +354,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
345
354
|
focusNextFormField: () => void;
|
|
346
355
|
isLastField: () => boolean;
|
|
347
356
|
ref?: React.RefObject<any> | React.ForwardedRef<any> | undefined;
|
|
357
|
+
suppressError?: boolean | undefined;
|
|
348
358
|
nextFormFieldRef?: React.RefObject<any> | undefined;
|
|
349
359
|
};
|
|
350
360
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import { isFocused } from '@react-native-ama/internal';
|
|
2
3
|
import { FormActions, FormProps } from './components/Form';
|
|
3
4
|
import { FormField, FormFieldProps } from './components/FormField';
|
|
4
5
|
import { FormSubmit, FormSubmitProps } from './components/FormSubmit';
|
|
5
|
-
import { FormSwitchProps } from './components/FormSwitch';
|
|
6
|
+
import { FormSwitch, FormSwitchProps } from './components/FormSwitch';
|
|
6
7
|
type FormComponent = React.FunctionComponent<FormProps> & {
|
|
7
8
|
Submit: (props: FormSubmitProps) => JSX.Element;
|
|
8
9
|
Field: React.FunctionComponent<FormFieldProps>;
|
|
9
10
|
Switch: (props: FormSwitchProps) => JSX.Element;
|
|
10
11
|
};
|
|
11
12
|
declare const Form: FormComponent;
|
|
12
|
-
export { Form, FormField, FormSubmit };
|
|
13
|
+
export { Form, FormField, FormSubmit, FormSwitch };
|
|
13
14
|
export { TextInput } from './components/TextInput';
|
|
14
15
|
export { useFormField } from './hooks/useFormField';
|
|
15
16
|
export { useTextInput } from './hooks/useTextInput';
|
|
17
|
+
export { isFocused };
|
|
16
18
|
export { type FormProps, type FormActions, type FormFieldProps, type FormSubmitProps, type FormSwitchProps, };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useTextInput = exports.useFormField = exports.TextInput = exports.FormSubmit = exports.FormField = exports.Form = void 0;
|
|
4
2
|
// Components
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.isFocused = exports.useTextInput = exports.useFormField = exports.TextInput = exports.FormSwitch = exports.FormSubmit = exports.FormField = exports.Form = void 0;
|
|
5
|
+
const internal_1 = require("@react-native-ama/internal");
|
|
6
|
+
Object.defineProperty(exports, "isFocused", { enumerable: true, get: function () { return internal_1.isFocused; } });
|
|
5
7
|
const Form_1 = require("./components/Form");
|
|
6
8
|
const FormField_1 = require("./components/FormField");
|
|
7
9
|
Object.defineProperty(exports, "FormField", { enumerable: true, get: function () { return FormField_1.FormField; } });
|
|
8
10
|
const FormSubmit_1 = require("./components/FormSubmit");
|
|
9
11
|
Object.defineProperty(exports, "FormSubmit", { enumerable: true, get: function () { return FormSubmit_1.FormSubmit; } });
|
|
10
12
|
const FormSwitch_1 = require("./components/FormSwitch");
|
|
13
|
+
Object.defineProperty(exports, "FormSwitch", { enumerable: true, get: function () { return FormSwitch_1.FormSwitch; } });
|
|
11
14
|
// @ts-ignore
|
|
12
15
|
const Form = Form_1.Form;
|
|
13
16
|
exports.Form = Form;
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-ama/forms",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"private": false,
|
|
3
|
+
"version": "1.1.0",
|
|
5
4
|
"react-native": "src/index",
|
|
6
5
|
"types": "dist/index.d.ts",
|
|
7
6
|
"main": "dist/index.js",
|
|
@@ -24,9 +23,9 @@
|
|
|
24
23
|
"test": "jest"
|
|
25
24
|
},
|
|
26
25
|
"dependencies": {
|
|
27
|
-
"@react-native-ama/core": "~1.
|
|
28
|
-
"@react-native-ama/internal": "~1.
|
|
29
|
-
"@react-native-ama/react-native": "~1.
|
|
26
|
+
"@react-native-ama/core": "~1.1.0",
|
|
27
|
+
"@react-native-ama/internal": "~1.1.0",
|
|
28
|
+
"@react-native-ama/react-native": "~1.1.0"
|
|
30
29
|
},
|
|
31
30
|
"peerDependencies": {
|
|
32
31
|
"react": "*",
|
|
@@ -47,7 +46,7 @@
|
|
|
47
46
|
},
|
|
48
47
|
"homepage": "https://github.com/FormidableLabs/react-native-ama#readme",
|
|
49
48
|
"publishConfig": {
|
|
50
|
-
"provenance":
|
|
49
|
+
"provenance": true
|
|
51
50
|
},
|
|
52
51
|
"author": "",
|
|
53
52
|
"license": "MIT",
|
package/src/components/Form.tsx
CHANGED
|
@@ -5,11 +5,12 @@ import { InteractionManager } from 'react-native';
|
|
|
5
5
|
|
|
6
6
|
export type FormProps = React.PropsWithChildren<{
|
|
7
7
|
onSubmit: () => boolean | Promise<boolean>;
|
|
8
|
-
ref?: React.RefObject<FormActions>;
|
|
8
|
+
ref?: React.RefObject<FormActions>; // need to explicitly type for inference in <Form /> component
|
|
9
9
|
}>;
|
|
10
10
|
|
|
11
11
|
export type FormActions = {
|
|
12
12
|
focusFirstInvalidField: () => void;
|
|
13
|
+
focusFieldAt: (fieldNumber: number) => void;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const Form = React.forwardRef<FormActions, FormProps>(
|
|
@@ -19,51 +20,83 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
19
20
|
|
|
20
21
|
const checks = __DEV__ ? useChecks?.() : undefined;
|
|
21
22
|
|
|
22
|
-
const focusField = (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
const focusField = React.useCallback(
|
|
24
|
+
(nextField?: Partial<FormRef> | undefined) => {
|
|
25
|
+
/**
|
|
26
|
+
* Refs passed as prop have another ".current"
|
|
27
|
+
*/
|
|
28
|
+
const nextRefElement = nextField?.ref?.current?.current
|
|
29
|
+
? nextField?.ref.current
|
|
30
|
+
: nextField?.ref;
|
|
31
|
+
|
|
32
|
+
const callFocus =
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
nextRefElement?.current?.focus &&
|
|
35
|
+
nextField?.hasFocusCallback &&
|
|
36
|
+
nextField?.isEditable;
|
|
37
|
+
|
|
38
|
+
__DEV__ &&
|
|
39
|
+
nextRefElement == null &&
|
|
40
|
+
checks?.logResult('nextRefElement', {
|
|
41
|
+
message:
|
|
42
|
+
'No next field found. Make sure you wrapped your form inside the <Form /> component',
|
|
43
|
+
rule: 'NO_UNDEFINED',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (callFocus) {
|
|
47
|
+
/**
|
|
48
|
+
* On some apps, if we call focus immediately and the field is already focused we lose the focus.
|
|
49
|
+
* Same happens if we do not call `focus` if the field is already focused.
|
|
50
|
+
*/
|
|
51
|
+
const timeoutValue = isFocused(nextRefElement.current) ? 50 : 0;
|
|
52
|
+
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
nextRefElement?.current?.focus();
|
|
55
|
+
|
|
56
|
+
__DEV__ &&
|
|
57
|
+
nextRefElement &&
|
|
58
|
+
checks?.checkFocusTrap({
|
|
59
|
+
ref: nextRefElement,
|
|
60
|
+
shouldHaveFocus: true,
|
|
61
|
+
});
|
|
62
|
+
}, timeoutValue);
|
|
63
|
+
} else if (nextRefElement?.current) {
|
|
64
|
+
setFocus(nextRefElement?.current);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
[checks, setFocus],
|
|
68
|
+
);
|
|
29
69
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
70
|
+
const focusFieldAt = React.useCallback(
|
|
71
|
+
(position: number) => {
|
|
72
|
+
InteractionManager.runAfterInteractions(() => {
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
const fieldWithError = refs.current[position];
|
|
75
|
+
|
|
76
|
+
focusField(fieldWithError);
|
|
77
|
+
}, 0);
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
[focusField],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const focusFirstInvalidField = React.useCallback(() => {
|
|
84
|
+
const fieldWithError = (refs.current || []).findIndex(
|
|
85
|
+
fieldRef => fieldRef.hasValidation && fieldRef.hasError,
|
|
86
|
+
);
|
|
35
87
|
|
|
36
88
|
__DEV__ &&
|
|
37
|
-
|
|
38
|
-
checks?.logResult('
|
|
89
|
+
fieldWithError == null &&
|
|
90
|
+
checks?.logResult('Form', {
|
|
39
91
|
message:
|
|
40
|
-
'
|
|
92
|
+
'The form validation has failed, but no component with error was found',
|
|
41
93
|
rule: 'NO_UNDEFINED',
|
|
42
94
|
});
|
|
43
95
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* On some apps, if we call focus immediately and the field is already focused we lose the focus.
|
|
47
|
-
* Same happens if we do not call `focus` if the field is already focused.
|
|
48
|
-
*/
|
|
49
|
-
const timeoutValue = isFocused(nextRefElement.current) ? 50 : 0;
|
|
50
|
-
|
|
51
|
-
setTimeout(() => {
|
|
52
|
-
nextRefElement?.current?.focus();
|
|
53
|
-
|
|
54
|
-
__DEV__ &&
|
|
55
|
-
nextRefElement &&
|
|
56
|
-
checks?.checkFocusTrap({
|
|
57
|
-
ref: nextRefElement,
|
|
58
|
-
shouldHaveFocus: true,
|
|
59
|
-
});
|
|
60
|
-
}, timeoutValue);
|
|
61
|
-
} else if (nextRefElement?.current) {
|
|
62
|
-
setFocus(nextRefElement?.current);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
96
|
+
focusFieldAt(fieldWithError);
|
|
97
|
+
}, [focusFieldAt, checks]);
|
|
65
98
|
|
|
66
|
-
const submitForm = async () => {
|
|
99
|
+
const submitForm = React.useCallback(async () => {
|
|
67
100
|
const isValid = await onSubmit();
|
|
68
101
|
|
|
69
102
|
if (isValid) {
|
|
@@ -71,30 +104,11 @@ export const Form = React.forwardRef<FormActions, FormProps>(
|
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
focusFirstInvalidField();
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const focusFirstInvalidField = () => {
|
|
77
|
-
InteractionManager.runAfterInteractions(() => {
|
|
78
|
-
setTimeout(() => {
|
|
79
|
-
const fieldWithError = (refs.current || []).find(
|
|
80
|
-
fieldRef => fieldRef.hasValidation && fieldRef.hasError,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
__DEV__ &&
|
|
84
|
-
fieldWithError == null &&
|
|
85
|
-
checks?.logResult('Form', {
|
|
86
|
-
message:
|
|
87
|
-
'The form validation has failed, but no component with error was found',
|
|
88
|
-
rule: 'NO_UNDEFINED',
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
focusField(fieldWithError);
|
|
92
|
-
}, 0);
|
|
93
|
-
});
|
|
94
|
-
};
|
|
107
|
+
}, [focusFirstInvalidField, onSubmit]);
|
|
95
108
|
|
|
96
109
|
React.useImperativeHandle(ref, () => ({
|
|
97
110
|
focusFirstInvalidField,
|
|
111
|
+
focusFieldAt,
|
|
98
112
|
}));
|
|
99
113
|
|
|
100
114
|
return (
|
|
@@ -123,8 +137,21 @@ export type FormRef = {
|
|
|
123
137
|
|
|
124
138
|
export const FormContext = React.createContext<FormContextValue | null>(null);
|
|
125
139
|
|
|
126
|
-
|
|
140
|
+
const DEFAULT_CONTEXT_VALUE: FormContextValue = {
|
|
141
|
+
refs: [],
|
|
142
|
+
submitForm: () => Promise.resolve(),
|
|
143
|
+
focusField: () => {},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const useForm = (
|
|
147
|
+
{ suppressError }: { suppressError?: boolean } = { suppressError: false },
|
|
148
|
+
) => {
|
|
127
149
|
const context = React.useContext(FormContext);
|
|
150
|
+
|
|
151
|
+
if (!context && suppressError) {
|
|
152
|
+
return DEFAULT_CONTEXT_VALUE; // return default values so internal useForm hooks don't throw undefined errors when user choose to suppress errors
|
|
153
|
+
}
|
|
154
|
+
|
|
128
155
|
if (!context) {
|
|
129
156
|
__DEV__ &&
|
|
130
157
|
console.error(
|
|
@@ -134,5 +161,6 @@ export const useForm = () => {
|
|
|
134
161
|
'useForm must be used within a FormContextProvider, please wrap your form field inside the <Form /> component',
|
|
135
162
|
);
|
|
136
163
|
}
|
|
164
|
+
|
|
137
165
|
return context;
|
|
138
166
|
};
|
|
@@ -32,7 +32,7 @@ describe('FormSubmit', () => {
|
|
|
32
32
|
);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it('hides the children from the
|
|
35
|
+
it('hides the children from the accessibility tree', () => {
|
|
36
36
|
const { getByTestId } = render(
|
|
37
37
|
<FormSubmit
|
|
38
38
|
accessibilityLabel="This is the label"
|
|
@@ -17,6 +17,7 @@ export type UseFormField = {
|
|
|
17
17
|
hasValidation: boolean;
|
|
18
18
|
hasError?: boolean;
|
|
19
19
|
errorMessage?: string;
|
|
20
|
+
suppressError?: boolean;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export const useFormField = ({
|
|
@@ -30,9 +31,10 @@ export const useFormField = ({
|
|
|
30
31
|
accessibilityHint,
|
|
31
32
|
errorMessage,
|
|
32
33
|
editable = true,
|
|
34
|
+
suppressError,
|
|
33
35
|
style = {},
|
|
34
36
|
}: UseFormField) => {
|
|
35
|
-
const { refs, submitForm, focusField } = useForm();
|
|
37
|
+
const { refs, submitForm, focusField } = useForm({ suppressError });
|
|
36
38
|
const fieldRef = React.useRef(ref);
|
|
37
39
|
|
|
38
40
|
const checks = __DEV__ ? useChecks?.() : undefined;
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// Components
|
|
2
|
+
|
|
3
|
+
import { isFocused } from '@react-native-ama/internal';
|
|
4
|
+
|
|
2
5
|
import {
|
|
3
6
|
FormActions,
|
|
4
7
|
FormProps,
|
|
@@ -20,7 +23,7 @@ Form.Submit = FormSubmit;
|
|
|
20
23
|
Form.Field = FormField;
|
|
21
24
|
Form.Switch = FormSwitch;
|
|
22
25
|
|
|
23
|
-
export { Form, FormField, FormSubmit };
|
|
26
|
+
export { Form, FormField, FormSubmit, FormSwitch };
|
|
24
27
|
|
|
25
28
|
// Components
|
|
26
29
|
export { TextInput } from './components/TextInput';
|
|
@@ -29,6 +32,9 @@ export { TextInput } from './components/TextInput';
|
|
|
29
32
|
export { useFormField } from './hooks/useFormField';
|
|
30
33
|
export { useTextInput } from './hooks/useTextInput';
|
|
31
34
|
|
|
35
|
+
// utils
|
|
36
|
+
export { isFocused };
|
|
37
|
+
|
|
32
38
|
// Types
|
|
33
39
|
export {
|
|
34
40
|
type FormProps,
|