@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
package/dist/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/dist/src/Form/Form.js
CHANGED
|
@@ -38,6 +38,7 @@ import { useScrollToError } from "./hooks/useScrollToError";
|
|
|
38
38
|
import { FormSaveButton } from "./components/FormSaveButton";
|
|
39
39
|
import { useSaveButtonPosition } from "./hooks/useSaveButtonPosition";
|
|
40
40
|
import { FormCache } from "./components/FormCache/FormCache";
|
|
41
|
+
import { isEdgeToEdgeEnabled } from "../utils/buildConfig/isEdgeToEdgeEnabled";
|
|
41
42
|
import { InputAccessoriesProvider } from "../InputText";
|
|
42
43
|
import { tokens } from "../utils/design";
|
|
43
44
|
import { ErrorMessageProvider } from "../ErrorMessageWrapper";
|
|
@@ -102,12 +103,14 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
|
|
|
102
103
|
setMessageBannerHeight(event.nativeEvent.layout.height);
|
|
103
104
|
};
|
|
104
105
|
const styles = useStyles();
|
|
106
|
+
// Check if edge-to-edge is enabled using utility function
|
|
107
|
+
const edgeToEdgeEnabled = isEdgeToEdgeEnabled();
|
|
105
108
|
return (React.createElement(FormProvider, Object.assign({}, formMethods),
|
|
106
109
|
React.createElement(React.Fragment, null,
|
|
107
110
|
(isSubmitting || isSecondaryActionLoading) && React.createElement(FormMask, null),
|
|
108
111
|
React.createElement(FormCache, { localCacheKey: localCacheKey, localCacheExclude: localCacheExclude, setLocalCache: setLocalCache }),
|
|
109
112
|
React.createElement(FormBody, { keyboardHeight: calculateSaveButtonOffset(), submit: handleSubmit(internalSubmit), isFormSubmitting: isSubmitting, saveButtonLabel: saveButtonLabel, shouldRenderActionBar: saveButtonPosition === "sticky", renderStickySection: renderStickySection, secondaryActions: secondaryActions, setSecondaryActionLoading: setIsSecondaryActionLoading, setSaveButtonHeight: setSaveButtonHeight, saveButtonOffset: saveButtonOffset },
|
|
110
|
-
React.createElement(KeyboardAwareScrollView, Object.assign({ enableResetScrollToCoords: false, enableAutomaticScroll: true, keyboardOpeningTime: Platform.OS === "ios" ? tokens["timing-slowest"] : 0, keyboardShouldPersistTaps: "handled", ref: scrollViewRef }, keyboardProps, { extraHeight: headerHeight, contentContainerStyle: !keyboardHeight && styles.scrollContentContainer }),
|
|
113
|
+
React.createElement(KeyboardAwareScrollView, Object.assign({ enableResetScrollToCoords: false, enableAutomaticScroll: true, enableOnAndroid: edgeToEdgeEnabled, keyboardOpeningTime: Platform.OS === "ios" ? tokens["timing-slowest"] : 0, keyboardShouldPersistTaps: "handled", ref: scrollViewRef }, keyboardProps, { extraHeight: headerHeight, extraScrollHeight: edgeToEdgeEnabled ? 20 : 0, contentContainerStyle: !keyboardHeight && styles.scrollContentContainer }),
|
|
111
114
|
React.createElement(View, { onLayout: ({ nativeEvent }) => {
|
|
112
115
|
setFormContentHeight(nativeEvent.layout.height);
|
|
113
116
|
} },
|
|
@@ -28,6 +28,5 @@ export function FormCache({ localCacheExclude, localCacheKey, setLocalCache, })
|
|
|
28
28
|
setLocalCache(formData);
|
|
29
29
|
}
|
|
30
30
|
}, [formData, isDirty, localCacheExclude, setLocalCache, shouldExclude]);
|
|
31
|
-
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
32
31
|
return React.createElement(React.Fragment, null);
|
|
33
32
|
}
|
|
@@ -24,31 +24,6 @@ const getKeyboard = (props) => {
|
|
|
24
24
|
return "numeric";
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
|
-
const computeDisplayFromNumericInput = (numberedValue, decimalNumbers, decimalCount, maxLength, maxDecimalPlaces, decimalPlaces, formatNumber) => {
|
|
28
|
-
const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
|
|
29
|
-
const stringValue = decimalNumbers !== ""
|
|
30
|
-
? transformedValue.toString() + "." + decimalNumbers.slice(1)
|
|
31
|
-
: transformedValue.toString();
|
|
32
|
-
if (checkLastChar(stringValue)) {
|
|
33
|
-
const roundedDecimal = configureDecimal(decimalCount, maxDecimalPlaces, stringValue, decimalPlaces);
|
|
34
|
-
const internationalizedValueToDisplay = formatNumber(roundedDecimal, {
|
|
35
|
-
maximumFractionDigits: maxDecimalPlaces,
|
|
36
|
-
});
|
|
37
|
-
return {
|
|
38
|
-
onChangeValue: roundedDecimal,
|
|
39
|
-
displayValue: internationalizedValueToDisplay,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
const internationalizedValueToDisplay = formatNumber(transformedValue, {
|
|
44
|
-
maximumFractionDigits: maxDecimalPlaces,
|
|
45
|
-
}) + decimalNumbers;
|
|
46
|
-
return {
|
|
47
|
-
onChangeValue: transformedValue.toString() + "." + decimalNumbers.slice(1),
|
|
48
|
-
displayValue: internationalizedValueToDisplay,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
27
|
export function InputCurrency(props) {
|
|
53
28
|
var _a, _b;
|
|
54
29
|
const { showCurrencySymbol = true, maxDecimalPlaces = 5, decimalPlaces = 2, maxLength = 10, } = props;
|
|
@@ -60,39 +35,52 @@ export function InputCurrency(props) {
|
|
|
60
35
|
});
|
|
61
36
|
const internalValue = getInternalValue(props, field, intl.formatNumber);
|
|
62
37
|
const [displayValue, setDisplayValue] = useState(internalValue);
|
|
38
|
+
const setOnChangeAndDisplayValues = (onChangeValue, valueToDisplay) => {
|
|
39
|
+
var _a;
|
|
40
|
+
(_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, onChangeValue);
|
|
41
|
+
setDisplayValue(valueToDisplay);
|
|
42
|
+
};
|
|
43
|
+
const checkDecimalAndI18nOfDisplayValue = (numberedValue, decimalNumbers, decimalCount) => {
|
|
44
|
+
const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
|
|
45
|
+
const stringValue = decimalNumbers !== ""
|
|
46
|
+
? transformedValue.toString() + "." + decimalNumbers.slice(1)
|
|
47
|
+
: transformedValue.toString();
|
|
48
|
+
if (checkLastChar(stringValue)) {
|
|
49
|
+
const roundedDecimal = configureDecimal(decimalCount, maxDecimalPlaces, stringValue, decimalPlaces);
|
|
50
|
+
const internationalizedValueToDisplay = intl.formatNumber(roundedDecimal, {
|
|
51
|
+
maximumFractionDigits: maxDecimalPlaces,
|
|
52
|
+
});
|
|
53
|
+
setOnChangeAndDisplayValues(roundedDecimal, internationalizedValueToDisplay);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const internationalizedValueToDisplay = intl.formatNumber(transformedValue, {
|
|
57
|
+
maximumFractionDigits: maxDecimalPlaces,
|
|
58
|
+
}) + decimalNumbers;
|
|
59
|
+
setOnChangeAndDisplayValues(transformedValue.toString() + "." + decimalNumbers.slice(1), internationalizedValueToDisplay);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
63
62
|
const handleChange = (newValue) => {
|
|
64
|
-
var _a, _b;
|
|
65
63
|
const [decimalCount, wholeIntegerValue, decimalNumbers] = parseGivenInput(newValue, floatSeparators.decimal);
|
|
66
64
|
const numberedValue = wholeIntegerValue
|
|
67
65
|
? convertToNumber(wholeIntegerValue)
|
|
68
66
|
: wholeIntegerValue;
|
|
69
67
|
if (isValidNumber(numberedValue) && typeof numberedValue === "number") {
|
|
70
|
-
|
|
71
|
-
const { onChangeValue, displayValue: valueToDisplay } = result;
|
|
72
|
-
(_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, onChangeValue);
|
|
73
|
-
setDisplayValue(valueToDisplay);
|
|
68
|
+
checkDecimalAndI18nOfDisplayValue(numberedValue, decimalNumbers, decimalCount);
|
|
74
69
|
}
|
|
75
70
|
else {
|
|
76
71
|
const value = (numberedValue === null || numberedValue === void 0 ? void 0 : numberedValue.toString()) + decimalNumbers;
|
|
77
|
-
(
|
|
78
|
-
setDisplayValue(value);
|
|
72
|
+
setOnChangeAndDisplayValues(value, value);
|
|
79
73
|
}
|
|
80
74
|
};
|
|
81
75
|
const { t } = useAtlantisI18n();
|
|
82
|
-
const defaultValidations = {
|
|
83
|
-
pattern: {
|
|
84
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
85
|
-
message: t("errors.notANumber"),
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
const mergedValidations = props.validations
|
|
89
|
-
? Object.assign({}, defaultValidations, props.validations)
|
|
90
|
-
: defaultValidations;
|
|
91
76
|
return (React.createElement(InputText, Object.assign({}, props, { prefix: showCurrencySymbol ? { label: currencySymbol } : undefined, keyboard: getKeyboard(props), value: ((_a = props.value) === null || _a === void 0 ? void 0 : _a.toString()) || displayValue, defaultValue: (_b = props.defaultValue) === null || _b === void 0 ? void 0 : _b.toString(), onChangeText: handleChange, transform: {
|
|
92
77
|
output: val => {
|
|
93
78
|
return val === null || val === void 0 ? void 0 : val.split(floatSeparators.group).join("").replace(floatSeparators.decimal, ".");
|
|
94
79
|
},
|
|
95
|
-
}, validations:
|
|
80
|
+
}, validations: Object.assign({ pattern: {
|
|
81
|
+
value: NUMBER_VALIDATION_REGEX,
|
|
82
|
+
message: t("errors.notANumber"),
|
|
83
|
+
} }, props.validations), onBlur: () => {
|
|
96
84
|
var _a;
|
|
97
85
|
(_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
98
86
|
if (field.value === 0 ||
|
|
@@ -2,14 +2,8 @@ import React, { forwardRef } from "react";
|
|
|
2
2
|
import { InputText } from "../InputText";
|
|
3
3
|
export const InputEmail = forwardRef(InputEmailInternal);
|
|
4
4
|
function InputEmailInternal(props, ref) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
};
|
|
11
|
-
const mergedValidations = props.validations
|
|
12
|
-
? Object.assign({}, defaultValidations, props.validations)
|
|
13
|
-
: defaultValidations;
|
|
14
|
-
return (React.createElement(InputText, Object.assign({}, props, { ref: ref, autoCapitalize: "none", autoCorrect: false, keyboard: "email-address", validations: mergedValidations })));
|
|
5
|
+
return (React.createElement(InputText, Object.assign({}, props, { ref: ref, autoCapitalize: "none", autoCorrect: false, keyboard: "email-address", validations: Object.assign({ pattern: {
|
|
6
|
+
value: /[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])?/,
|
|
7
|
+
message: "Enter a valid email address (email@example.com)",
|
|
8
|
+
} }, props.validations) })));
|
|
15
9
|
}
|
|
@@ -24,19 +24,13 @@ function InputNumberInternal(props, ref) {
|
|
|
24
24
|
(_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, newValue);
|
|
25
25
|
};
|
|
26
26
|
const { inputTransform: convertToString, outputTransform: convertToNumber } = useNumberTransform(props.value);
|
|
27
|
-
const defaultValidations = {
|
|
28
|
-
pattern: {
|
|
29
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
30
|
-
message: t("errors.notANumber"),
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
const mergedValidations = props.validations
|
|
34
|
-
? Object.assign({}, defaultValidations, props.validations)
|
|
35
|
-
: defaultValidations;
|
|
36
27
|
return (React.createElement(InputText, Object.assign({}, props, { keyboard: getKeyboard(), transform: {
|
|
37
28
|
input: flow(convertToString, ((_a = props.transform) === null || _a === void 0 ? void 0 : _a.input) || identity),
|
|
38
29
|
output: flow(convertToNumber, ((_b = props.transform) === null || _b === void 0 ? void 0 : _b.output) || identity),
|
|
39
|
-
}, ref: ref, value: (_c = props.value) === null || _c === void 0 ? void 0 : _c.toString(), defaultValue: (_d = props.defaultValue) === null || _d === void 0 ? void 0 : _d.toString(), onChangeText: handleChange, validations:
|
|
30
|
+
}, ref: ref, value: (_c = props.value) === null || _c === void 0 ? void 0 : _c.toString(), defaultValue: (_d = props.defaultValue) === null || _d === void 0 ? void 0 : _d.toString(), onChangeText: handleChange, validations: Object.assign({ pattern: {
|
|
31
|
+
value: NUMBER_VALIDATION_REGEX,
|
|
32
|
+
message: t("errors.notANumber"),
|
|
33
|
+
} }, props.validations) })));
|
|
40
34
|
}
|
|
41
35
|
function hasPeriodAtEnd(value) {
|
|
42
36
|
// matches patterns like ".", "0.", "12.", "+1.", and "-0."
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { v1 } from "react-native-uuid";
|
|
2
|
-
import {
|
|
2
|
+
import { useController, useForm, useFormContext } from "react-hook-form";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
export function useFormController({ name, value, validations, }) {
|
|
5
|
+
var _a, _b;
|
|
5
6
|
const fieldName = useControlName(name);
|
|
6
7
|
const form = useForm({
|
|
7
8
|
mode: "onTouched",
|
|
@@ -16,10 +17,18 @@ export function useFormController({ name, value, validations, }) {
|
|
|
16
17
|
defaultValue: value || undefined,
|
|
17
18
|
});
|
|
18
19
|
// The naming convention established by react-hook-form for arrays of fields is, for example, "emails.0.description".
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
const
|
|
20
|
+
// This corresponds to the structure of the error object, e.g. { emails: { 0: { description: "foobar" } } }
|
|
21
|
+
// 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").
|
|
22
|
+
// TODO: Add support for two-part identifiers (e.g. "property.province")
|
|
23
|
+
const fieldIdentifiers = fieldName.split(".");
|
|
24
|
+
let error;
|
|
25
|
+
if (fieldIdentifiers.length === 3) {
|
|
26
|
+
const [section, item, identifier] = fieldIdentifiers;
|
|
27
|
+
error = (_b = (_a = errors[section]) === null || _a === void 0 ? void 0 : _a[item]) === null || _b === void 0 ? void 0 : _b[identifier];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
error = errors[fieldName];
|
|
31
|
+
}
|
|
23
32
|
return { error, field };
|
|
24
33
|
}
|
|
25
34
|
function useControlName(name) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
function loadBuildConfig() {
|
|
3
|
+
var _a;
|
|
4
|
+
try {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
6
|
+
const mod = require("react-native-build-config");
|
|
7
|
+
return ((_a = mod === null || mod === void 0 ? void 0 : mod.default) !== null && _a !== void 0 ? _a : mod);
|
|
8
|
+
}
|
|
9
|
+
catch (_b) {
|
|
10
|
+
return null; // module not installed or not linked
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function isEdgeToEdgeEnabled() {
|
|
14
|
+
if (Platform.OS !== "android")
|
|
15
|
+
return false;
|
|
16
|
+
const cfg = loadBuildConfig();
|
|
17
|
+
return !!(cfg === null || cfg === void 0 ? void 0 : cfg.IS_EDGE_TO_EDGE_ENABLED);
|
|
18
|
+
}
|