@jobber/components-native 0.85.1-JOB-124062-569fcb4.27 → 0.85.1-JOB-136074-0fe613a.28
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/package.json +9 -3
- package/dist/src/Form/Form.js +4 -1
- package/dist/src/Form/components/FormCache/FormCache.js +0 -1
- package/dist/src/InputCurrency/InputCurrency.js +30 -42
- package/dist/src/InputEmail/InputEmail.js +4 -10
- package/dist/src/InputNumber/InputNumber.js +4 -10
- package/dist/src/hooks/useFormController.js +14 -5
- package/dist/src/utils/buildConfig/isEdgeToEdgeEnabled.js +18 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/Form/components/FormCache/FormCache.d.ts +2 -2
- package/dist/types/src/Form/context/types.d.ts +2 -2
- package/dist/types/src/Form/hooks/useInternalForm.d.ts +2 -2
- package/dist/types/src/Form/types.d.ts +3 -3
- package/dist/types/src/utils/buildConfig/isEdgeToEdgeEnabled.d.ts +1 -0
- package/package.json +9 -3
- package/src/Form/Form.tsx +6 -0
- package/src/Form/components/FormCache/FormCache.tsx +3 -4
- package/src/Form/context/types.ts +2 -2
- package/src/Form/hooks/useInternalForm.ts +1 -2
- package/src/Form/types.ts +4 -3
- package/src/InputCurrency/InputCurrency.tsx +57 -71
- package/src/InputEmail/InputEmail.tsx +8 -12
- package/src/InputNumber/InputNumber.tsx +7 -11
- package/src/hooks/useFormController.ts +13 -6
- package/src/types/buildConfig.d.ts +7 -0
- package/src/utils/buildConfig/isEdgeToEdgeEnabled.ts +21 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FieldValues } from "react-hook-form";
|
|
2
2
|
interface FormCacheProps<T extends FieldValues> {
|
|
3
3
|
readonly localCacheId?: string | string[];
|
|
4
4
|
readonly localCacheKey?: string;
|
|
5
5
|
readonly localCacheExclude?: string[];
|
|
6
|
-
readonly setLocalCache: (data:
|
|
6
|
+
readonly setLocalCache: (data: T) => void;
|
|
7
7
|
}
|
|
8
8
|
export declare function FormCache<T extends FieldValues>({ localCacheExclude, localCacheKey, setLocalCache, }: FormCacheProps<T>): JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MutableRefObject } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FieldValues, UseFormReturn } from "react-hook-form";
|
|
3
3
|
export interface UseConfirmBeforeBackProps {
|
|
4
4
|
alwaysPreventBack: boolean;
|
|
5
5
|
onAcceptEvent?: () => void;
|
|
@@ -18,7 +18,7 @@ interface LocalCacheOptions {
|
|
|
18
18
|
export interface AtlantisFormContextProps {
|
|
19
19
|
useConfirmBeforeBack: (props: UseConfirmBeforeBackProps) => MutableRefObject<() => void>;
|
|
20
20
|
useInternalFormLocalCache: <TData extends FieldValues>(formMethods: UseFormReturn<TData>, cacheKey?: string, options?: LocalCacheOptions) => {
|
|
21
|
-
setLocalCache: (data:
|
|
21
|
+
setLocalCache: (data: TData) => void;
|
|
22
22
|
removeLocalCache: () => void;
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FieldValues, UseFormHandleSubmit, UseFormReturn } from "react-hook-form";
|
|
2
2
|
import type { MutableRefObject, RefObject } from "react";
|
|
3
3
|
import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
4
4
|
import type { InternalFormProps } from "../types";
|
|
@@ -13,7 +13,7 @@ interface UseInternalForm<T extends FieldValues> {
|
|
|
13
13
|
readonly isSubmitting: boolean;
|
|
14
14
|
readonly isDirty: boolean;
|
|
15
15
|
readonly removeListenerRef: MutableRefObject<() => void>;
|
|
16
|
-
readonly setLocalCache: (data:
|
|
16
|
+
readonly setLocalCache: (data: T) => void;
|
|
17
17
|
}
|
|
18
18
|
export declare function useInternalForm<T extends FieldValues, SubmitResponseType>({ mode, reValidateMode, initialValues, formRef, localCacheKey, localCacheId, scrollViewRef, saveButtonHeight, messageBannerHeight, }: UseInternalFormProps<T, SubmitResponseType>): UseInternalForm<T>;
|
|
19
19
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { MutableRefObject, RefObject } from "react";
|
|
2
|
-
import type { ControllerProps,
|
|
2
|
+
import type { ControllerProps, DeepPartial, FieldPath, FieldValues, Mode, UnpackNestedValue, UseFormReturn } from "react-hook-form";
|
|
3
3
|
import type { IconNames } from "@jobber/design";
|
|
4
4
|
import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
5
|
-
export type FormValues<T> = T
|
|
5
|
+
export type FormValues<T> = UnpackNestedValue<T>;
|
|
6
6
|
export type FormErrors = FormNetworkErrors | FormUserErrors;
|
|
7
7
|
export type FormBannerMessage = FormWarningMessage | FormNoticeMessage;
|
|
8
8
|
export declare enum FormSubmitErrorType {
|
|
@@ -81,7 +81,7 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
|
|
|
81
81
|
* The initial values of the form inputs
|
|
82
82
|
* This should be available as soon as initialLoading is set to false
|
|
83
83
|
*/
|
|
84
|
-
initialValues?:
|
|
84
|
+
initialValues?: FormValues<DeepPartial<T>>;
|
|
85
85
|
/**
|
|
86
86
|
* When the validation should happen.
|
|
87
87
|
* Possible values are "onBlur", "onChange", "onSubmit", "onTouched", and "all".
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isEdgeToEdgeEnabled(): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.85.1-JOB-
|
|
3
|
+
"version": "0.85.1-JOB-136074-0fe613a.28+0fe613a84",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"autolinker": "^4.0.0",
|
|
44
44
|
"deepmerge": "^4.2.2",
|
|
45
45
|
"lodash": "^4.17.21",
|
|
46
|
-
"react-hook-form": "^7.
|
|
46
|
+
"react-hook-form": "^7.30.0",
|
|
47
47
|
"react-intl": "^6.4.2",
|
|
48
48
|
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
|
49
49
|
"react-native-modalize": "^2.0.13",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"react": "^18.2.0",
|
|
87
87
|
"react-intl": "^6.4.2",
|
|
88
88
|
"react-native": ">=0.76.0",
|
|
89
|
+
"react-native-build-config": "^0.3.2",
|
|
89
90
|
"react-native-gesture-handler": ">=2.10.0",
|
|
90
91
|
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
|
91
92
|
"react-native-modal-datetime-picker": " >=13.0.0",
|
|
@@ -95,5 +96,10 @@
|
|
|
95
96
|
"react-native-safe-area-context": "^5.4.0",
|
|
96
97
|
"react-native-svg": ">=12.0.0"
|
|
97
98
|
},
|
|
98
|
-
"
|
|
99
|
+
"peerDependenciesMeta": {
|
|
100
|
+
"react-native-build-config": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"gitHead": "0fe613a84c9d804a249c128d7cd35ea5a6cbda38"
|
|
99
105
|
}
|
package/src/Form/Form.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import { useScrollToError } from "./hooks/useScrollToError";
|
|
|
26
26
|
import { FormSaveButton } from "./components/FormSaveButton";
|
|
27
27
|
import { useSaveButtonPosition } from "./hooks/useSaveButtonPosition";
|
|
28
28
|
import { FormCache } from "./components/FormCache/FormCache";
|
|
29
|
+
import { isEdgeToEdgeEnabled } from "../utils/buildConfig/isEdgeToEdgeEnabled";
|
|
29
30
|
import { InputAccessoriesProvider } from "../InputText";
|
|
30
31
|
import { tokens } from "../utils/design";
|
|
31
32
|
import { ErrorMessageProvider } from "../ErrorMessageWrapper";
|
|
@@ -134,6 +135,9 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
134
135
|
|
|
135
136
|
const styles = useStyles();
|
|
136
137
|
|
|
138
|
+
// Check if edge-to-edge is enabled using utility function
|
|
139
|
+
const edgeToEdgeEnabled = isEdgeToEdgeEnabled();
|
|
140
|
+
|
|
137
141
|
return (
|
|
138
142
|
<FormProvider {...formMethods}>
|
|
139
143
|
<>
|
|
@@ -161,6 +165,7 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
161
165
|
<KeyboardAwareScrollView
|
|
162
166
|
enableResetScrollToCoords={false}
|
|
163
167
|
enableAutomaticScroll={true}
|
|
168
|
+
enableOnAndroid={edgeToEdgeEnabled}
|
|
164
169
|
keyboardOpeningTime={
|
|
165
170
|
Platform.OS === "ios" ? tokens["timing-slowest"] : 0
|
|
166
171
|
}
|
|
@@ -168,6 +173,7 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
168
173
|
ref={scrollViewRef}
|
|
169
174
|
{...keyboardProps}
|
|
170
175
|
extraHeight={headerHeight}
|
|
176
|
+
extraScrollHeight={edgeToEdgeEnabled ? 20 : 0}
|
|
171
177
|
contentContainerStyle={
|
|
172
178
|
!keyboardHeight && styles.scrollContentContainer
|
|
173
179
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FieldValues } from "react-hook-form";
|
|
3
3
|
import { useFormContext, useWatch } from "react-hook-form";
|
|
4
4
|
import omit from "lodash/omit";
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ interface FormCacheProps<T extends FieldValues> {
|
|
|
7
7
|
readonly localCacheId?: string | string[];
|
|
8
8
|
readonly localCacheKey?: string;
|
|
9
9
|
readonly localCacheExclude?: string[];
|
|
10
|
-
readonly setLocalCache: (data:
|
|
10
|
+
readonly setLocalCache: (data: T) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function FormCache<T extends FieldValues>({
|
|
@@ -18,7 +18,7 @@ export function FormCache<T extends FieldValues>({
|
|
|
18
18
|
const { control, formState } = useFormContext<T>();
|
|
19
19
|
const { isDirty } = formState;
|
|
20
20
|
|
|
21
|
-
const formData = useWatch({ control });
|
|
21
|
+
const formData = useWatch<T>({ control });
|
|
22
22
|
const shouldExclude = useMemo(() => {
|
|
23
23
|
return Array.isArray(localCacheExclude) && localCacheExclude.length > 0;
|
|
24
24
|
}, [localCacheExclude]);
|
|
@@ -46,6 +46,5 @@ export function FormCache<T extends FieldValues>({
|
|
|
46
46
|
}
|
|
47
47
|
}, [formData, isDirty, localCacheExclude, setLocalCache, shouldExclude]);
|
|
48
48
|
|
|
49
|
-
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
50
49
|
return <></>;
|
|
51
50
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MutableRefObject } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FieldValues, UseFormReturn } from "react-hook-form";
|
|
3
3
|
|
|
4
4
|
export interface UseConfirmBeforeBackProps {
|
|
5
5
|
alwaysPreventBack: boolean;
|
|
@@ -27,7 +27,7 @@ export interface AtlantisFormContextProps {
|
|
|
27
27
|
cacheKey?: string,
|
|
28
28
|
options?: LocalCacheOptions,
|
|
29
29
|
) => {
|
|
30
|
-
setLocalCache: (data:
|
|
30
|
+
setLocalCache: (data: TData) => void;
|
|
31
31
|
removeLocalCache: () => void;
|
|
32
32
|
};
|
|
33
33
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
DeepPartial,
|
|
3
2
|
FieldValues,
|
|
4
3
|
UseFormHandleSubmit,
|
|
5
4
|
UseFormReturn,
|
|
@@ -32,7 +31,7 @@ interface UseInternalForm<T extends FieldValues> {
|
|
|
32
31
|
readonly isSubmitting: boolean;
|
|
33
32
|
readonly isDirty: boolean;
|
|
34
33
|
readonly removeListenerRef: MutableRefObject<() => void>;
|
|
35
|
-
readonly setLocalCache: (data:
|
|
34
|
+
readonly setLocalCache: (data: T) => void;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
export function useInternalForm<T extends FieldValues, SubmitResponseType>({
|
package/src/Form/types.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import type { MutableRefObject, RefObject } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
ControllerProps,
|
|
4
|
-
|
|
4
|
+
DeepPartial,
|
|
5
5
|
FieldPath,
|
|
6
6
|
FieldValues,
|
|
7
7
|
Mode,
|
|
8
|
+
UnpackNestedValue,
|
|
8
9
|
UseFormReturn,
|
|
9
10
|
} from "react-hook-form";
|
|
10
11
|
import type { IconNames } from "@jobber/design";
|
|
11
12
|
import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
12
13
|
|
|
13
|
-
export type FormValues<T> = T
|
|
14
|
+
export type FormValues<T> = UnpackNestedValue<T>;
|
|
14
15
|
export type FormErrors = FormNetworkErrors | FormUserErrors;
|
|
15
16
|
export type FormBannerMessage = FormWarningMessage | FormNoticeMessage;
|
|
16
17
|
|
|
@@ -108,7 +109,7 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
|
|
|
108
109
|
* The initial values of the form inputs
|
|
109
110
|
* This should be available as soon as initialLoading is set to false
|
|
110
111
|
*/
|
|
111
|
-
initialValues?:
|
|
112
|
+
initialValues?: FormValues<DeepPartial<T>>;
|
|
112
113
|
|
|
113
114
|
/**
|
|
114
115
|
* When the validation should happen.
|
|
@@ -80,56 +80,6 @@ const getKeyboard = (props: InputCurrencyProps) => {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
const computeDisplayFromNumericInput = (
|
|
84
|
-
numberedValue: number,
|
|
85
|
-
decimalNumbers: string,
|
|
86
|
-
decimalCount: number,
|
|
87
|
-
maxLength: number,
|
|
88
|
-
maxDecimalPlaces: number,
|
|
89
|
-
decimalPlaces: number,
|
|
90
|
-
formatNumber: (
|
|
91
|
-
value: number,
|
|
92
|
-
opts?: FormatNumberOptions | undefined,
|
|
93
|
-
) => string,
|
|
94
|
-
): {
|
|
95
|
-
onChangeValue: number | string;
|
|
96
|
-
displayValue: string;
|
|
97
|
-
} => {
|
|
98
|
-
const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
|
|
99
|
-
const stringValue =
|
|
100
|
-
decimalNumbers !== ""
|
|
101
|
-
? transformedValue.toString() + "." + decimalNumbers.slice(1)
|
|
102
|
-
: transformedValue.toString();
|
|
103
|
-
|
|
104
|
-
if (checkLastChar(stringValue)) {
|
|
105
|
-
const roundedDecimal = configureDecimal(
|
|
106
|
-
decimalCount,
|
|
107
|
-
maxDecimalPlaces,
|
|
108
|
-
stringValue,
|
|
109
|
-
decimalPlaces,
|
|
110
|
-
);
|
|
111
|
-
const internationalizedValueToDisplay = formatNumber(roundedDecimal, {
|
|
112
|
-
maximumFractionDigits: maxDecimalPlaces,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
onChangeValue: roundedDecimal,
|
|
117
|
-
displayValue: internationalizedValueToDisplay,
|
|
118
|
-
};
|
|
119
|
-
} else {
|
|
120
|
-
const internationalizedValueToDisplay =
|
|
121
|
-
formatNumber(transformedValue, {
|
|
122
|
-
maximumFractionDigits: maxDecimalPlaces,
|
|
123
|
-
}) + decimalNumbers;
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
onChangeValue:
|
|
127
|
-
transformedValue.toString() + "." + decimalNumbers.slice(1),
|
|
128
|
-
displayValue: internationalizedValueToDisplay,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
83
|
export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
134
84
|
const {
|
|
135
85
|
showCurrencySymbol = true,
|
|
@@ -149,6 +99,54 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
149
99
|
internalValue,
|
|
150
100
|
);
|
|
151
101
|
|
|
102
|
+
const setOnChangeAndDisplayValues = (
|
|
103
|
+
onChangeValue: number | string | undefined,
|
|
104
|
+
valueToDisplay: string | undefined,
|
|
105
|
+
) => {
|
|
106
|
+
props.onChange?.(onChangeValue);
|
|
107
|
+
setDisplayValue(valueToDisplay);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const checkDecimalAndI18nOfDisplayValue = (
|
|
111
|
+
numberedValue: number,
|
|
112
|
+
decimalNumbers: string,
|
|
113
|
+
decimalCount: number,
|
|
114
|
+
) => {
|
|
115
|
+
const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
|
|
116
|
+
const stringValue =
|
|
117
|
+
decimalNumbers !== ""
|
|
118
|
+
? transformedValue.toString() + "." + decimalNumbers.slice(1)
|
|
119
|
+
: transformedValue.toString();
|
|
120
|
+
|
|
121
|
+
if (checkLastChar(stringValue)) {
|
|
122
|
+
const roundedDecimal = configureDecimal(
|
|
123
|
+
decimalCount,
|
|
124
|
+
maxDecimalPlaces,
|
|
125
|
+
stringValue,
|
|
126
|
+
decimalPlaces,
|
|
127
|
+
);
|
|
128
|
+
const internationalizedValueToDisplay = intl.formatNumber(
|
|
129
|
+
roundedDecimal,
|
|
130
|
+
{
|
|
131
|
+
maximumFractionDigits: maxDecimalPlaces,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
setOnChangeAndDisplayValues(
|
|
135
|
+
roundedDecimal,
|
|
136
|
+
internationalizedValueToDisplay,
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
const internationalizedValueToDisplay =
|
|
140
|
+
intl.formatNumber(transformedValue, {
|
|
141
|
+
maximumFractionDigits: maxDecimalPlaces,
|
|
142
|
+
}) + decimalNumbers;
|
|
143
|
+
setOnChangeAndDisplayValues(
|
|
144
|
+
transformedValue.toString() + "." + decimalNumbers.slice(1),
|
|
145
|
+
internationalizedValueToDisplay,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
152
150
|
const handleChange = (newValue: string | undefined) => {
|
|
153
151
|
const [decimalCount, wholeIntegerValue, decimalNumbers] = parseGivenInput(
|
|
154
152
|
newValue,
|
|
@@ -160,37 +158,19 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
160
158
|
: wholeIntegerValue;
|
|
161
159
|
|
|
162
160
|
if (isValidNumber(numberedValue) && typeof numberedValue === "number") {
|
|
163
|
-
|
|
161
|
+
checkDecimalAndI18nOfDisplayValue(
|
|
164
162
|
numberedValue,
|
|
165
163
|
decimalNumbers,
|
|
166
164
|
decimalCount,
|
|
167
|
-
maxLength,
|
|
168
|
-
maxDecimalPlaces,
|
|
169
|
-
decimalPlaces,
|
|
170
|
-
intl.formatNumber,
|
|
171
165
|
);
|
|
172
|
-
const { onChangeValue, displayValue: valueToDisplay } = result;
|
|
173
|
-
props.onChange?.(onChangeValue);
|
|
174
|
-
setDisplayValue(valueToDisplay);
|
|
175
166
|
} else {
|
|
176
167
|
const value = numberedValue?.toString() + decimalNumbers;
|
|
177
|
-
|
|
178
|
-
setDisplayValue(value);
|
|
168
|
+
setOnChangeAndDisplayValues(value, value);
|
|
179
169
|
}
|
|
180
170
|
};
|
|
181
171
|
|
|
182
172
|
const { t } = useAtlantisI18n();
|
|
183
173
|
|
|
184
|
-
const defaultValidations = {
|
|
185
|
-
pattern: {
|
|
186
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
187
|
-
message: t("errors.notANumber"),
|
|
188
|
-
},
|
|
189
|
-
} as const;
|
|
190
|
-
const mergedValidations = props.validations
|
|
191
|
-
? Object.assign({}, defaultValidations, props.validations)
|
|
192
|
-
: defaultValidations;
|
|
193
|
-
|
|
194
174
|
return (
|
|
195
175
|
<InputText
|
|
196
176
|
{...props}
|
|
@@ -207,7 +187,13 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
207
187
|
.replace(floatSeparators.decimal, ".");
|
|
208
188
|
},
|
|
209
189
|
}}
|
|
210
|
-
validations={
|
|
190
|
+
validations={{
|
|
191
|
+
pattern: {
|
|
192
|
+
value: NUMBER_VALIDATION_REGEX,
|
|
193
|
+
message: t("errors.notANumber"),
|
|
194
|
+
},
|
|
195
|
+
...props.validations,
|
|
196
|
+
}}
|
|
211
197
|
onBlur={() => {
|
|
212
198
|
props.onBlur?.();
|
|
213
199
|
|
|
@@ -7,17 +7,6 @@ export const InputEmail = forwardRef(InputEmailInternal);
|
|
|
7
7
|
type InputEmailProps = Omit<InputTextProps, "keyboard">;
|
|
8
8
|
|
|
9
9
|
function InputEmailInternal(props: InputEmailProps, ref: Ref<InputTextRef>) {
|
|
10
|
-
const defaultValidations = {
|
|
11
|
-
pattern: {
|
|
12
|
-
value:
|
|
13
|
-
/[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?/,
|
|
14
|
-
message: "Enter a valid email address (email@example.com)",
|
|
15
|
-
},
|
|
16
|
-
} as const;
|
|
17
|
-
const mergedValidations = props.validations
|
|
18
|
-
? Object.assign({}, defaultValidations, props.validations)
|
|
19
|
-
: defaultValidations;
|
|
20
|
-
|
|
21
10
|
return (
|
|
22
11
|
<InputText
|
|
23
12
|
{...props}
|
|
@@ -25,7 +14,14 @@ function InputEmailInternal(props: InputEmailProps, ref: Ref<InputTextRef>) {
|
|
|
25
14
|
autoCapitalize="none"
|
|
26
15
|
autoCorrect={false}
|
|
27
16
|
keyboard={"email-address"}
|
|
28
|
-
validations={
|
|
17
|
+
validations={{
|
|
18
|
+
pattern: {
|
|
19
|
+
value:
|
|
20
|
+
/[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?/,
|
|
21
|
+
message: "Enter a valid email address (email@example.com)",
|
|
22
|
+
},
|
|
23
|
+
...props.validations,
|
|
24
|
+
}}
|
|
29
25
|
/>
|
|
30
26
|
);
|
|
31
27
|
}
|
|
@@ -46,16 +46,6 @@ function InputNumberInternal(props: InputNumberProps, ref: Ref<InputTextRef>) {
|
|
|
46
46
|
const { inputTransform: convertToString, outputTransform: convertToNumber } =
|
|
47
47
|
useNumberTransform(props.value);
|
|
48
48
|
|
|
49
|
-
const defaultValidations = {
|
|
50
|
-
pattern: {
|
|
51
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
52
|
-
message: t("errors.notANumber"),
|
|
53
|
-
},
|
|
54
|
-
} as const;
|
|
55
|
-
const mergedValidations = props.validations
|
|
56
|
-
? Object.assign({}, defaultValidations, props.validations)
|
|
57
|
-
: defaultValidations;
|
|
58
|
-
|
|
59
49
|
return (
|
|
60
50
|
<InputText
|
|
61
51
|
{...props}
|
|
@@ -68,7 +58,13 @@ function InputNumberInternal(props: InputNumberProps, ref: Ref<InputTextRef>) {
|
|
|
68
58
|
value={props.value?.toString()}
|
|
69
59
|
defaultValue={props.defaultValue?.toString()}
|
|
70
60
|
onChangeText={handleChange}
|
|
71
|
-
validations={
|
|
61
|
+
validations={{
|
|
62
|
+
pattern: {
|
|
63
|
+
value: NUMBER_VALIDATION_REGEX,
|
|
64
|
+
message: t("errors.notANumber"),
|
|
65
|
+
},
|
|
66
|
+
...props.validations,
|
|
67
|
+
}}
|
|
72
68
|
/>
|
|
73
69
|
);
|
|
74
70
|
}
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
RegisterOptions,
|
|
5
5
|
UseControllerReturn,
|
|
6
6
|
} from "react-hook-form";
|
|
7
|
-
import {
|
|
7
|
+
import { useController, useForm, useFormContext } from "react-hook-form";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
|
|
10
10
|
interface UseFormControllerProps<T> {
|
|
@@ -44,11 +44,18 @@ export function useFormController<T>({
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
// The naming convention established by react-hook-form for arrays of fields is, for example, "emails.0.description".
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
47
|
+
// This corresponds to the structure of the error object, e.g. { emails: { 0: { description: "foobar" } } }
|
|
48
|
+
// We assume here that fields will either follow this period-delimited three-part convention, or else that they are simple and indivisible (e.g. "city").
|
|
49
|
+
// TODO: Add support for two-part identifiers (e.g. "property.province")
|
|
50
|
+
const fieldIdentifiers = fieldName.split(".");
|
|
51
|
+
let error: FieldError | undefined;
|
|
52
|
+
|
|
53
|
+
if (fieldIdentifiers.length === 3) {
|
|
54
|
+
const [section, item, identifier] = fieldIdentifiers;
|
|
55
|
+
error = errors[section]?.[item]?.[identifier];
|
|
56
|
+
} else {
|
|
57
|
+
error = errors[fieldName];
|
|
58
|
+
}
|
|
52
59
|
|
|
53
60
|
return { error, field };
|
|
54
61
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
type BuildCfg = { IS_EDGE_TO_EDGE_ENABLED?: boolean } | undefined | null;
|
|
4
|
+
|
|
5
|
+
function loadBuildConfig(): BuildCfg {
|
|
6
|
+
try {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
8
|
+
const mod = require("react-native-build-config");
|
|
9
|
+
|
|
10
|
+
return (mod?.default ?? mod) as BuildCfg;
|
|
11
|
+
} catch {
|
|
12
|
+
return null; // module not installed or not linked
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isEdgeToEdgeEnabled(): boolean {
|
|
17
|
+
if (Platform.OS !== "android") return false;
|
|
18
|
+
const cfg = loadBuildConfig();
|
|
19
|
+
|
|
20
|
+
return !!cfg?.IS_EDGE_TO_EDGE_ENABLED;
|
|
21
|
+
}
|