@jobber/components-native 0.86.1-JOB-136074-a7a2b82.0 → 0.86.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/package.json +3 -9
- package/dist/src/Form/Form.js +1 -4
- package/dist/src/Form/components/FormCache/FormCache.js +1 -0
- package/dist/src/InputCurrency/InputCurrency.js +42 -30
- package/dist/src/InputEmail/InputEmail.js +12 -4
- package/dist/src/InputNumber/InputNumber.js +10 -4
- package/dist/src/hooks/useAtlantisI18n/locales/en.json +1 -0
- package/dist/src/hooks/useAtlantisI18n/locales/es.json +1 -0
- package/dist/src/hooks/useFormController.js +5 -14
- 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/package.json +3 -9
- package/src/Form/Form.tsx +0 -6
- package/src/Form/components/FormCache/FormCache.tsx +4 -3
- package/src/Form/context/types.ts +2 -2
- package/src/Form/hooks/useInternalForm.ts +2 -1
- package/src/Form/types.ts +3 -4
- package/src/InputCurrency/InputCurrency.tsx +71 -57
- package/src/InputEmail/InputEmail.tsx +15 -8
- package/src/InputNumber/InputNumber.tsx +11 -7
- package/src/hooks/useAtlantisI18n/locales/en.json +1 -0
- package/src/hooks/useAtlantisI18n/locales/es.json +1 -0
- package/src/hooks/useFormController.ts +6 -13
- package/dist/src/utils/buildConfig/isEdgeToEdgeEnabled.js +0 -17
- package/dist/types/src/utils/buildConfig/isEdgeToEdgeEnabled.d.ts +0 -1
- package/src/types/buildConfig.d.ts +0 -7
- package/src/utils/buildConfig/isEdgeToEdgeEnabled.ts +0 -20
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { FieldValues } from "react-hook-form";
|
|
1
|
+
import type { DeepPartial, 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: T) => void;
|
|
6
|
+
readonly setLocalCache: (data: DeepPartial<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 { FieldValues, UseFormReturn } from "react-hook-form";
|
|
2
|
+
import type { DeepPartial, 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: TData) => void;
|
|
21
|
+
setLocalCache: (data: DeepPartial<TData>) => void;
|
|
22
22
|
removeLocalCache: () => void;
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldValues, UseFormHandleSubmit, UseFormReturn } from "react-hook-form";
|
|
1
|
+
import type { DeepPartial, 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: T) => void;
|
|
16
|
+
readonly setLocalCache: (data: DeepPartial<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, DefaultValues, FieldPath, FieldValues, Mode, 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> =
|
|
5
|
+
export type FormValues<T> = 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?: DefaultValues<T>;
|
|
85
85
|
/**
|
|
86
86
|
* When the validation should happen.
|
|
87
87
|
* Possible values are "onBlur", "onChange", "onSubmit", "onTouched", and "all".
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.86.1
|
|
3
|
+
"version": "0.86.1",
|
|
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.52.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,7 +86,6 @@
|
|
|
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",
|
|
90
89
|
"react-native-gesture-handler": ">=2.10.0",
|
|
91
90
|
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
|
92
91
|
"react-native-modal-datetime-picker": " >=13.0.0",
|
|
@@ -96,10 +95,5 @@
|
|
|
96
95
|
"react-native-safe-area-context": "^5.4.0",
|
|
97
96
|
"react-native-svg": ">=12.0.0"
|
|
98
97
|
},
|
|
99
|
-
"
|
|
100
|
-
"react-native-build-config": {
|
|
101
|
-
"optional": true
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
"gitHead": "a7a2b828816e7a2eae21ead0e0eba40d494e284a"
|
|
98
|
+
"gitHead": "ca472a375fdf3c6b8e8596c3c26a3958893093ec"
|
|
105
99
|
}
|
package/src/Form/Form.tsx
CHANGED
|
@@ -26,7 +26,6 @@ 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";
|
|
30
29
|
import { InputAccessoriesProvider } from "../InputText";
|
|
31
30
|
import { tokens } from "../utils/design";
|
|
32
31
|
import { ErrorMessageProvider } from "../ErrorMessageWrapper";
|
|
@@ -135,9 +134,6 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
135
134
|
|
|
136
135
|
const styles = useStyles();
|
|
137
136
|
|
|
138
|
-
// Check if edge-to-edge is enabled using utility function
|
|
139
|
-
const edgeToEdgeEnabled = isEdgeToEdgeEnabled();
|
|
140
|
-
|
|
141
137
|
return (
|
|
142
138
|
<FormProvider {...formMethods}>
|
|
143
139
|
<>
|
|
@@ -165,7 +161,6 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
165
161
|
<KeyboardAwareScrollView
|
|
166
162
|
enableResetScrollToCoords={false}
|
|
167
163
|
enableAutomaticScroll={true}
|
|
168
|
-
enableOnAndroid={edgeToEdgeEnabled}
|
|
169
164
|
keyboardOpeningTime={
|
|
170
165
|
Platform.OS === "ios" ? tokens["timing-slowest"] : 0
|
|
171
166
|
}
|
|
@@ -173,7 +168,6 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
173
168
|
ref={scrollViewRef}
|
|
174
169
|
{...keyboardProps}
|
|
175
170
|
extraHeight={headerHeight}
|
|
176
|
-
extraScrollHeight={edgeToEdgeEnabled ? 20 : 0}
|
|
177
171
|
contentContainerStyle={
|
|
178
172
|
!keyboardHeight && styles.scrollContentContainer
|
|
179
173
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
|
-
import type { FieldValues } from "react-hook-form";
|
|
2
|
+
import type { DeepPartial, 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: T) => void;
|
|
10
|
+
readonly setLocalCache: (data: DeepPartial<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
|
|
21
|
+
const formData = useWatch({ control });
|
|
22
22
|
const shouldExclude = useMemo(() => {
|
|
23
23
|
return Array.isArray(localCacheExclude) && localCacheExclude.length > 0;
|
|
24
24
|
}, [localCacheExclude]);
|
|
@@ -46,5 +46,6 @@ 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
|
|
49
50
|
return <></>;
|
|
50
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MutableRefObject } from "react";
|
|
2
|
-
import type { FieldValues, UseFormReturn } from "react-hook-form";
|
|
2
|
+
import type { DeepPartial, 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: TData) => void;
|
|
30
|
+
setLocalCache: (data: DeepPartial<TData>) => void;
|
|
31
31
|
removeLocalCache: () => void;
|
|
32
32
|
};
|
|
33
33
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DeepPartial,
|
|
2
3
|
FieldValues,
|
|
3
4
|
UseFormHandleSubmit,
|
|
4
5
|
UseFormReturn,
|
|
@@ -31,7 +32,7 @@ interface UseInternalForm<T extends FieldValues> {
|
|
|
31
32
|
readonly isSubmitting: boolean;
|
|
32
33
|
readonly isDirty: boolean;
|
|
33
34
|
readonly removeListenerRef: MutableRefObject<() => void>;
|
|
34
|
-
readonly setLocalCache: (data: T) => void;
|
|
35
|
+
readonly setLocalCache: (data: DeepPartial<T>) => void;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export function useInternalForm<T extends FieldValues, SubmitResponseType>({
|
package/src/Form/types.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import type { MutableRefObject, RefObject } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
ControllerProps,
|
|
4
|
-
|
|
4
|
+
DefaultValues,
|
|
5
5
|
FieldPath,
|
|
6
6
|
FieldValues,
|
|
7
7
|
Mode,
|
|
8
|
-
UnpackNestedValue,
|
|
9
8
|
UseFormReturn,
|
|
10
9
|
} from "react-hook-form";
|
|
11
10
|
import type { IconNames } from "@jobber/design";
|
|
12
11
|
import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
13
12
|
|
|
14
|
-
export type FormValues<T> =
|
|
13
|
+
export type FormValues<T> = T;
|
|
15
14
|
export type FormErrors = FormNetworkErrors | FormUserErrors;
|
|
16
15
|
export type FormBannerMessage = FormWarningMessage | FormNoticeMessage;
|
|
17
16
|
|
|
@@ -109,7 +108,7 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
|
|
|
109
108
|
* The initial values of the form inputs
|
|
110
109
|
* This should be available as soon as initialLoading is set to false
|
|
111
110
|
*/
|
|
112
|
-
initialValues?:
|
|
111
|
+
initialValues?: DefaultValues<T>;
|
|
113
112
|
|
|
114
113
|
/**
|
|
115
114
|
* When the validation should happen.
|
|
@@ -80,6 +80,56 @@ 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
|
+
|
|
83
133
|
export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
84
134
|
const {
|
|
85
135
|
showCurrencySymbol = true,
|
|
@@ -99,54 +149,6 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
99
149
|
internalValue,
|
|
100
150
|
);
|
|
101
151
|
|
|
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
|
-
|
|
150
152
|
const handleChange = (newValue: string | undefined) => {
|
|
151
153
|
const [decimalCount, wholeIntegerValue, decimalNumbers] = parseGivenInput(
|
|
152
154
|
newValue,
|
|
@@ -158,19 +160,37 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
158
160
|
: wholeIntegerValue;
|
|
159
161
|
|
|
160
162
|
if (isValidNumber(numberedValue) && typeof numberedValue === "number") {
|
|
161
|
-
|
|
163
|
+
const result = computeDisplayFromNumericInput(
|
|
162
164
|
numberedValue,
|
|
163
165
|
decimalNumbers,
|
|
164
166
|
decimalCount,
|
|
167
|
+
maxLength,
|
|
168
|
+
maxDecimalPlaces,
|
|
169
|
+
decimalPlaces,
|
|
170
|
+
intl.formatNumber,
|
|
165
171
|
);
|
|
172
|
+
const { onChangeValue, displayValue: valueToDisplay } = result;
|
|
173
|
+
props.onChange?.(onChangeValue);
|
|
174
|
+
setDisplayValue(valueToDisplay);
|
|
166
175
|
} else {
|
|
167
176
|
const value = numberedValue?.toString() + decimalNumbers;
|
|
168
|
-
|
|
177
|
+
props.onChange?.(value);
|
|
178
|
+
setDisplayValue(value);
|
|
169
179
|
}
|
|
170
180
|
};
|
|
171
181
|
|
|
172
182
|
const { t } = useAtlantisI18n();
|
|
173
183
|
|
|
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
|
+
|
|
174
194
|
return (
|
|
175
195
|
<InputText
|
|
176
196
|
{...props}
|
|
@@ -187,13 +207,7 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
187
207
|
.replace(floatSeparators.decimal, ".");
|
|
188
208
|
},
|
|
189
209
|
}}
|
|
190
|
-
validations={
|
|
191
|
-
pattern: {
|
|
192
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
193
|
-
message: t("errors.notANumber"),
|
|
194
|
-
},
|
|
195
|
-
...props.validations,
|
|
196
|
-
}}
|
|
210
|
+
validations={mergedValidations}
|
|
197
211
|
onBlur={() => {
|
|
198
212
|
props.onBlur?.();
|
|
199
213
|
|
|
@@ -2,11 +2,25 @@ import type { Ref } from "react";
|
|
|
2
2
|
import React, { forwardRef } from "react";
|
|
3
3
|
import type { InputTextProps, InputTextRef } from "../InputText";
|
|
4
4
|
import { InputText } from "../InputText";
|
|
5
|
+
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
|
|
5
6
|
|
|
6
7
|
export const InputEmail = forwardRef(InputEmailInternal);
|
|
7
8
|
type InputEmailProps = Omit<InputTextProps, "keyboard">;
|
|
8
9
|
|
|
9
10
|
function InputEmailInternal(props: InputEmailProps, ref: Ref<InputTextRef>) {
|
|
11
|
+
const { t } = useAtlantisI18n();
|
|
12
|
+
|
|
13
|
+
const defaultValidations = {
|
|
14
|
+
pattern: {
|
|
15
|
+
value:
|
|
16
|
+
/[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])?/,
|
|
17
|
+
message: t("InputEmail.enterEmail"),
|
|
18
|
+
},
|
|
19
|
+
} as const;
|
|
20
|
+
const mergedValidations = props.validations
|
|
21
|
+
? Object.assign({}, defaultValidations, props.validations)
|
|
22
|
+
: defaultValidations;
|
|
23
|
+
|
|
10
24
|
return (
|
|
11
25
|
<InputText
|
|
12
26
|
{...props}
|
|
@@ -14,14 +28,7 @@ function InputEmailInternal(props: InputEmailProps, ref: Ref<InputTextRef>) {
|
|
|
14
28
|
autoCapitalize="none"
|
|
15
29
|
autoCorrect={false}
|
|
16
30
|
keyboard={"email-address"}
|
|
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
|
-
}}
|
|
31
|
+
validations={mergedValidations}
|
|
25
32
|
/>
|
|
26
33
|
);
|
|
27
34
|
}
|
|
@@ -46,6 +46,16 @@ 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
|
+
|
|
49
59
|
return (
|
|
50
60
|
<InputText
|
|
51
61
|
{...props}
|
|
@@ -58,13 +68,7 @@ function InputNumberInternal(props: InputNumberProps, ref: Ref<InputTextRef>) {
|
|
|
58
68
|
value={props.value?.toString()}
|
|
59
69
|
defaultValue={props.defaultValue?.toString()}
|
|
60
70
|
onChangeText={handleChange}
|
|
61
|
-
validations={
|
|
62
|
-
pattern: {
|
|
63
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
64
|
-
message: t("errors.notANumber"),
|
|
65
|
-
},
|
|
66
|
-
...props.validations,
|
|
67
|
-
}}
|
|
71
|
+
validations={mergedValidations}
|
|
68
72
|
/>
|
|
69
73
|
);
|
|
70
74
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"goBack": "Go back",
|
|
19
19
|
"InputFieldWrapper.clear": "Clear input",
|
|
20
20
|
"InputPassword.enterPassword": "Enter a password",
|
|
21
|
+
"InputEmail.enterEmail": "Enter a valid email address (email@example.com)",
|
|
21
22
|
"loading": "Loading",
|
|
22
23
|
"menu": "Menu",
|
|
23
24
|
"more": "More",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"goBack": "Volver",
|
|
19
19
|
"InputFieldWrapper.clear": "Borrar",
|
|
20
20
|
"InputPassword.enterPassword": "Escriba una contraseña",
|
|
21
|
+
"InputEmail.enterEmail": "Introduzca una dirección de correo electrónico válida (email@example.com)",
|
|
21
22
|
"loading": "Cargando",
|
|
22
23
|
"menu": "Menú",
|
|
23
24
|
"more": "Más",
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
RegisterOptions,
|
|
5
5
|
UseControllerReturn,
|
|
6
6
|
} from "react-hook-form";
|
|
7
|
-
import { useController, useForm, useFormContext } from "react-hook-form";
|
|
7
|
+
import { get, useController, useForm, useFormContext } from "react-hook-form";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
|
|
10
10
|
interface UseFormControllerProps<T> {
|
|
@@ -44,18 +44,11 @@ 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
|
-
|
|
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
|
-
}
|
|
47
|
+
// Preserve original behavior: only treat three-part names as nested paths.
|
|
48
|
+
// For anything else, perform a flat lookup to avoid behavioral changes.
|
|
49
|
+
const identifiers = fieldName.split(".");
|
|
50
|
+
const error =
|
|
51
|
+
identifiers.length === 3 ? get(errors, fieldName) : errors[fieldName];
|
|
59
52
|
|
|
60
53
|
return { error, field };
|
|
61
54
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Platform } from "react-native";
|
|
2
|
-
function loadBuildConfig() {
|
|
3
|
-
var _a;
|
|
4
|
-
try {
|
|
5
|
-
const mod = require("react-native-build-config");
|
|
6
|
-
return ((_a = mod === null || mod === void 0 ? void 0 : mod.default) !== null && _a !== void 0 ? _a : mod);
|
|
7
|
-
}
|
|
8
|
-
catch (_b) {
|
|
9
|
-
return null; // module not installed or not linked
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export function isEdgeToEdgeEnabled() {
|
|
13
|
-
if (Platform.OS !== "android")
|
|
14
|
-
return false;
|
|
15
|
-
const cfg = loadBuildConfig();
|
|
16
|
-
return !!(cfg === null || cfg === void 0 ? void 0 : cfg.IS_EDGE_TO_EDGE_ENABLED);
|
|
17
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function isEdgeToEdgeEnabled(): boolean;
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
const mod = require("react-native-build-config");
|
|
8
|
-
|
|
9
|
-
return (mod?.default ?? mod) as BuildCfg;
|
|
10
|
-
} catch {
|
|
11
|
-
return null; // module not installed or not linked
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function isEdgeToEdgeEnabled(): boolean {
|
|
16
|
-
if (Platform.OS !== "android") return false;
|
|
17
|
-
const cfg = loadBuildConfig();
|
|
18
|
-
|
|
19
|
-
return !!cfg?.IS_EDGE_TO_EDGE_ENABLED;
|
|
20
|
-
}
|