@react-native-ama/forms 1.0.1-alpha.2 → 1.0.1
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 +12 -6
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -2
- package/package.json +5 -5
- 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;
|
|
@@ -45,6 +45,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
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;
|
|
@@ -152,9 +153,10 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
152
153
|
rejectResponderTermination?: boolean | null | undefined;
|
|
153
154
|
selectionState?: import("react-native").DocumentSelectionState | undefined;
|
|
154
155
|
spellCheck?: boolean | undefined;
|
|
155
|
-
textContentType?: "none" | "
|
|
156
|
+
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
157
|
scrollEnabled?: boolean | undefined;
|
|
157
158
|
lineBreakStrategyIOS?: "none" | "standard" | "hangul-word" | "push-out" | undefined;
|
|
159
|
+
smartInsertDelete?: boolean | undefined;
|
|
158
160
|
cursorColor?: import("react-native").ColorValue | null | undefined;
|
|
159
161
|
importantForAutofill?: "auto" | "yes" | "no" | "noExcludeDescendants" | "yesExcludeDescendants" | undefined;
|
|
160
162
|
disableFullscreenUI?: boolean | undefined;
|
|
@@ -166,7 +168,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
166
168
|
underlineColorAndroid?: import("react-native").ColorValue | undefined;
|
|
167
169
|
textAlignVertical?: "center" | "auto" | "top" | "bottom" | undefined;
|
|
168
170
|
showSoftInputOnFocus?: boolean | undefined;
|
|
169
|
-
verticalAlign?: "auto" | "
|
|
171
|
+
verticalAlign?: "auto" | "top" | "bottom" | "middle" | undefined;
|
|
170
172
|
labelComponent?: JSX.Element | undefined;
|
|
171
173
|
labelPosition?: "beforeInput" | "afterInput" | undefined;
|
|
172
174
|
nextFormField?: React.RefObject<import("react-native").TextInput> | undefined;
|
|
@@ -179,11 +181,12 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
179
181
|
focusNextFormField: () => void;
|
|
180
182
|
isLastField: () => boolean;
|
|
181
183
|
ref?: React.RefObject<any> | React.ForwardedRef<any> | undefined;
|
|
184
|
+
suppressError?: boolean | undefined;
|
|
182
185
|
nextFormFieldRef?: React.RefObject<any> | undefined;
|
|
183
186
|
} | {
|
|
184
187
|
allowFontScaling?: boolean | undefined;
|
|
185
188
|
autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined;
|
|
186
|
-
autoComplete?: "off" | "
|
|
189
|
+
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
190
|
autoCorrect?: boolean | undefined;
|
|
188
191
|
autoFocus?: boolean | undefined;
|
|
189
192
|
blurOnSubmit?: boolean | undefined;
|
|
@@ -210,6 +213,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
210
213
|
onKeyPress?: ((e: NativeSyntheticEvent<import("react-native").TextInputKeyPressEventData>) => void) | undefined;
|
|
211
214
|
placeholder?: string | undefined;
|
|
212
215
|
placeholderTextColor?: import("react-native").ColorValue | undefined;
|
|
216
|
+
readOnly?: boolean | undefined;
|
|
213
217
|
returnKeyType?: ReturnKeyTypeOptions | undefined;
|
|
214
218
|
enterKeyHint?: import("react-native").EnterKeyHintTypeOptions | undefined;
|
|
215
219
|
secureTextEntry?: boolean | undefined;
|
|
@@ -318,9 +322,10 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
318
322
|
rejectResponderTermination?: boolean | null | undefined;
|
|
319
323
|
selectionState?: import("react-native").DocumentSelectionState | undefined;
|
|
320
324
|
spellCheck?: boolean | undefined;
|
|
321
|
-
textContentType?: "none" | "
|
|
325
|
+
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
326
|
scrollEnabled?: boolean | undefined;
|
|
323
327
|
lineBreakStrategyIOS?: "none" | "standard" | "hangul-word" | "push-out" | undefined;
|
|
328
|
+
smartInsertDelete?: boolean | undefined;
|
|
324
329
|
cursorColor?: import("react-native").ColorValue | null | undefined;
|
|
325
330
|
importantForAutofill?: "auto" | "yes" | "no" | "noExcludeDescendants" | "yesExcludeDescendants" | undefined;
|
|
326
331
|
disableFullscreenUI?: boolean | undefined;
|
|
@@ -332,7 +337,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
332
337
|
underlineColorAndroid?: import("react-native").ColorValue | undefined;
|
|
333
338
|
textAlignVertical?: "center" | "auto" | "top" | "bottom" | undefined;
|
|
334
339
|
showSoftInputOnFocus?: boolean | undefined;
|
|
335
|
-
verticalAlign?: "auto" | "
|
|
340
|
+
verticalAlign?: "auto" | "top" | "bottom" | "middle" | undefined;
|
|
336
341
|
labelComponent?: JSX.Element | undefined;
|
|
337
342
|
labelPosition?: "beforeInput" | "afterInput" | undefined;
|
|
338
343
|
nextFormField?: React.RefObject<import("react-native").TextInput> | undefined;
|
|
@@ -345,6 +350,7 @@ export declare const useTextInput: ({ onLayout, returnKeyType, onSubmitEditing,
|
|
|
345
350
|
focusNextFormField: () => void;
|
|
346
351
|
isLastField: () => boolean;
|
|
347
352
|
ref?: React.RefObject<any> | React.ForwardedRef<any> | undefined;
|
|
353
|
+
suppressError?: boolean | undefined;
|
|
348
354
|
nextFormFieldRef?: React.RefObject<any> | undefined;
|
|
349
355
|
};
|
|
350
356
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-ama/forms",
|
|
3
|
-
"version": "1.0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"react-native": "src/index",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"test": "jest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@react-native-ama/core": "~1.0.1
|
|
28
|
-
"@react-native-ama/internal": "~1.0.1
|
|
29
|
-
"@react-native-ama/react-native": "~1.0.1
|
|
27
|
+
"@react-native-ama/core": "~1.0.1",
|
|
28
|
+
"@react-native-ama/internal": "~1.0.1",
|
|
29
|
+
"@react-native-ama/react-native": "~1.0.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": "*",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://github.com/FormidableLabs/react-native-ama#readme",
|
|
49
49
|
"publishConfig": {
|
|
50
|
-
"provenance":
|
|
50
|
+
"provenance": true
|
|
51
51
|
},
|
|
52
52
|
"author": "",
|
|
53
53
|
"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,
|