@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.
@@ -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: () => FormContextValue;
25
+ export declare const useForm: ({ suppressError }?: {
26
+ suppressError?: boolean | undefined;
27
+ }) => FormContextValue;
@@ -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 submitForm = () => __awaiter(void 0, void 0, void 0, function* () {
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 = (refs.current || []).find(fieldRef => fieldRef.hasValidation && fieldRef.hasError);
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 useForm = () => {
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" | "email" | "password" | "tel" | "url" | "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" | "country" | "current-password" | "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-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-country-code" | "tel-national" | "tel-device" | "username" | "username-new" | undefined;
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
- needsOffscreenAlphaCompositing?: boolean | undefined;
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" | "password" | "name" | "nickname" | "username" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "newPassword" | "oneTimeCode" | undefined;
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" | "middle" | "top" | "bottom" | undefined;
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" | "email" | "password" | "tel" | "url" | "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" | "country" | "current-password" | "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-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-country-code" | "tel-national" | "tel-device" | "username" | "username-new" | undefined;
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
- needsOffscreenAlphaCompositing?: boolean | undefined;
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" | "password" | "name" | "nickname" | "username" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "newPassword" | "oneTimeCode" | undefined;
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" | "middle" | "top" | "bottom" | undefined;
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.0.1-alpha.2",
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.0.1-alpha.1",
28
- "@react-native-ama/internal": "~1.0.1-alpha.1",
29
- "@react-native-ama/react-native": "~1.0.1-alpha.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": false
49
+ "provenance": true
51
50
  },
52
51
  "author": "",
53
52
  "license": "MIT",
@@ -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 = (nextField?: Partial<FormRef> | undefined) => {
23
- /**
24
- * Refs passed as prop have another ".current"
25
- */
26
- const nextRefElement = nextField?.ref?.current?.current
27
- ? nextField?.ref.current
28
- : nextField?.ref;
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
- const callFocus =
31
- // @ts-ignore
32
- nextRefElement?.current?.focus &&
33
- nextField?.hasFocusCallback &&
34
- nextField?.isEditable;
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
- nextRefElement == null &&
38
- checks?.logResult('nextRefElement', {
89
+ fieldWithError == null &&
90
+ checks?.logResult('Form', {
39
91
  message:
40
- 'No next field found. Make sure you wrapped your form inside the <Form /> component',
92
+ 'The form validation has failed, but no component with error was found',
41
93
  rule: 'NO_UNDEFINED',
42
94
  });
43
95
 
44
- if (callFocus) {
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
- export const useForm = () => {
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 accessiblity tree', () => {
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,