@abihealth/goapp-react-native 1.45.0 → 1.45.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.45.1](https://github.com/abiglobalhealth/react-native-sdk/compare/goapp-react-native-v1.45.0...goapp-react-native-v1.45.1) (2025-10-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * ABI-10439 sdk accessibility error messages are not announced ([#252](https://github.com/abiglobalhealth/react-native-sdk/issues/252)) ([6b68c48](https://github.com/abiglobalhealth/react-native-sdk/commit/6b68c48f1f2c96a48d005ca481cc859cc10ad36b))
9
+
3
10
  ## [1.45.0](https://github.com/abiglobalhealth/react-native-sdk/compare/goapp-react-native-v1.44.0...goapp-react-native-v1.45.0) (2025-10-16)
4
11
 
5
12
 
@@ -29,8 +29,17 @@ var Input = function (_a) {
29
29
  var theme = (0, useTheme_1.useTheme)();
30
30
  var Input = (0, useCustomComponents_1.useCustomComponents)().Input;
31
31
  var styles = getStyles(theme);
32
- var _b = (0, useForm_1.useForm)(), errors = _b.errors, data = _b.data, clearError = _b.clearError, updateData = _b.updateData, loading = _b.loading;
32
+ var _b = (0, useForm_1.useForm)(), errors = _b.errors, data = _b.data, clearError = _b.clearError, updateData = _b.updateData, loading = _b.loading, registerInput = _b.registerInput;
33
33
  var _c = (0, react_1.useState)(false), focused = _c[0], setFocused = _c[1];
34
+ var inputRef = (0, react_1.useRef)(null);
35
+ (0, react_1.useEffect)(function () {
36
+ if (id && registerInput && inputRef.current)
37
+ registerInput(id, inputRef.current);
38
+ return function () {
39
+ if (id && registerInput)
40
+ registerInput(id, null);
41
+ };
42
+ }, [id, registerInput]);
34
43
  var onFocus = function (e) {
35
44
  var _a;
36
45
  setFocused(true);
@@ -53,7 +62,7 @@ var Input = function (_a) {
53
62
  var placeholderColor = (Input === null || Input === void 0 ? void 0 : Input.placeholderTextColor) || theme.palette.grey.base;
54
63
  return (<FormControl_1.default label={label} error={err} name={id}>
55
64
  <react_native_1.View style={{ position: 'relative' }}>
56
- <react_native_1.TextInput {...props} id={id} style={[styles.input, focused && styles.focused, err && styles.error, props.style, Input === null || Input === void 0 ? void 0 : Input.containerStyle]} onFocus={onFocus} onBlur={onBlur} placeholderTextColor={placeholderColor} value={formValue || props.value} onChangeText={onChangeText} editable={!loading} accessibilityLabel={label ? "".concat(label, " input text field") : 'Input text field'} accessibilityLabelledBy={id} accessibilityState={{ busy: loading, disabled: loading }}/>
65
+ <react_native_1.TextInput {...props} ref={inputRef} id={id} style={[styles.input, focused && styles.focused, err && styles.error, props.style, Input === null || Input === void 0 ? void 0 : Input.containerStyle]} onFocus={onFocus} onBlur={onBlur} placeholderTextColor={placeholderColor} value={formValue || props.value} onChangeText={onChangeText} editable={!loading} accessibilityLabel={label ? "".concat(label, " input text field") : 'Input text field'} accessibilityLabelledBy={id} accessibilityState={{ busy: loading, disabled: loading }}/>
57
66
 
58
67
  {props.multiline && !!minLength && (!formValue || (formValue === null || formValue === void 0 ? void 0 : formValue.length) < minLength) && (<react_native_1.View style={styles.hintContainer}>
59
68
  <Text_1.Text color={placeholderColor}>{t('inputs.textarea.min_length', { minLength: minLength })}</Text_1.Text>
@@ -1,4 +1,5 @@
1
- import React, { PropsWithChildren } from 'react';
1
+ import React, { Dispatch, MutableRefObject, PropsWithChildren, SetStateAction } from 'react';
2
+ import { TextInput } from 'react-native';
2
3
  import { z } from 'zod';
3
4
  export type HandleSubmitProps<T extends z.AnyZodObject> = {
4
5
  onSuccess?: (data: z.TypeOf<T>) => Promise<void>;
@@ -7,14 +8,16 @@ export type HandleSubmitProps<T extends z.AnyZodObject> = {
7
8
  };
8
9
  export type FormContextProps<T extends z.AnyZodObject> = {
9
10
  data: z.infer<T> | null;
10
- setData: React.Dispatch<React.SetStateAction<z.infer<T> | null>>;
11
+ setData: Dispatch<SetStateAction<z.infer<T> | null>>;
11
12
  errors: Record<keyof z.infer<T>, string> | null;
12
- setErrors: React.Dispatch<React.SetStateAction<Record<keyof z.infer<T>, string> | null>>;
13
+ setErrors: Dispatch<SetStateAction<Record<keyof z.infer<T>, string> | null>>;
13
14
  schema: T;
14
15
  onSubmit?: HandleSubmitProps<T>;
15
16
  loading: boolean;
16
- setLoading: React.Dispatch<React.SetStateAction<boolean>>;
17
+ setLoading: Dispatch<SetStateAction<boolean>>;
17
18
  loadingForm?: boolean;
19
+ inputRefs: MutableRefObject<Map<string, TextInput | null>>;
20
+ registerInput: (id: string, ref: TextInput | null) => void;
18
21
  };
19
22
  export declare const FormContext: React.Context<FormContextProps<any>>;
20
23
  type FormProviderProps<T extends z.AnyZodObject> = {
@@ -44,14 +44,22 @@ exports.FormContext = (0, react_1.createContext)({
44
44
  schema: zod_1.z.object({}),
45
45
  loading: false,
46
46
  setLoading: function () { },
47
- loadingForm: false
47
+ loadingForm: false,
48
+ inputRefs: { current: new Map() },
49
+ registerInput: function () { }
48
50
  });
49
51
  var FormProvider = function (_a) {
50
52
  var children = _a.children, schema = _a.schema, initialValues = _a.initialValues, onSubmit = _a.onSubmit, loadingForm = _a.loadingForm;
51
53
  var _b = (0, react_1.useState)(false), loading = _b[0], setLoading = _b[1];
52
54
  var _c = (0, react_1.useState)(initialValues || null), data = _c[0], setData = _c[1];
53
55
  var _d = (0, react_1.useState)(null), errors = _d[0], setErrors = _d[1];
54
- return (<exports.FormContext.Provider value={{ data: data, setData: setData, errors: errors, setErrors: setErrors, schema: schema, onSubmit: onSubmit, loading: loading, setLoading: setLoading, loadingForm: loadingForm }}>
56
+ var inputRefs = (0, react_1.useRef)(new Map());
57
+ var registerInput = (0, react_1.useCallback)(function (id, ref) {
58
+ if (ref)
59
+ return inputRefs.current.set(id, ref);
60
+ inputRefs.current.delete(id);
61
+ }, []);
62
+ return (<exports.FormContext.Provider value={{ data: data, setData: setData, errors: errors, setErrors: setErrors, schema: schema, onSubmit: onSubmit, loading: loading, setLoading: setLoading, loadingForm: loadingForm, inputRefs: inputRefs, registerInput: registerInput }}>
55
63
  {children}
56
64
  </exports.FormContext.Provider>);
57
65
  };
@@ -7,4 +7,5 @@ export declare const useForm: <T extends z.AnyZodObject>() => {
7
7
  handleSubmit: () => Promise<void>;
8
8
  loading: boolean;
9
9
  setLoading: import("react").Dispatch<import("react").SetStateAction<boolean>>;
10
+ registerInput: (id: string, ref: import("react-native").TextInput | null) => void;
10
11
  };
@@ -51,6 +51,7 @@ exports.useForm = void 0;
51
51
  var FormContext_1 = require("../contexts/FormContext");
52
52
  var zod_1 = require("../utils/zod");
53
53
  var react_1 = require("react");
54
+ var react_native_1 = require("react-native");
54
55
  var useForm = function () {
55
56
  var context = (0, react_1.useContext)(FormContext_1.FormContext);
56
57
  var data = context.data, setData = context.setData, errors = context.errors, setErrors = context.setErrors, schema = context.schema, onSubmit = context.onSubmit, loading = context.loading, setLoading = context.setLoading, loadingForm = context.loadingForm;
@@ -67,27 +68,38 @@ var useForm = function () {
67
68
  });
68
69
  };
69
70
  var handleSubmit = function () { return __awaiter(void 0, void 0, void 0, function () {
70
- var error_1, parsedErrors;
71
- var _a, _b, _c;
72
- return __generator(this, function (_d) {
73
- switch (_d.label) {
71
+ var error_1, parsedErrors_1, currentRefs, firstErrorField_1, inputRef_1;
72
+ var _a, _b, _c, _d;
73
+ return __generator(this, function (_e) {
74
+ switch (_e.label) {
74
75
  case 0:
75
- _d.trys.push([0, 2, 4, 6]);
76
+ _e.trys.push([0, 2, 4, 6]);
76
77
  setLoading(true);
77
78
  schema.parse(data || {});
78
79
  return [4 /*yield*/, ((_a = onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit.onSuccess) === null || _a === void 0 ? void 0 : _a.call(onSubmit, data))];
79
80
  case 1:
80
- _d.sent();
81
+ _e.sent();
81
82
  return [3 /*break*/, 6];
82
83
  case 2:
83
- error_1 = _d.sent();
84
- parsedErrors = (0, zod_1.parseErrors)(error_1);
85
- setErrors(parsedErrors);
86
- return [4 /*yield*/, ((_b = onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit.onError) === null || _b === void 0 ? void 0 : _b.call(onSubmit, parsedErrors))];
87
- case 3: return [2 /*return*/, _d.sent()];
88
- case 4: return [4 /*yield*/, ((_c = onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit.onFinally) === null || _c === void 0 ? void 0 : _c.call(onSubmit))];
84
+ error_1 = _e.sent();
85
+ parsedErrors_1 = (0, zod_1.parseErrors)(error_1);
86
+ setErrors(parsedErrors_1);
87
+ currentRefs = (_b = context.inputRefs) === null || _b === void 0 ? void 0 : _b.current;
88
+ if (currentRefs) {
89
+ firstErrorField_1 = Array.from(currentRefs.keys()).find(function (key) { return parsedErrors_1[key]; });
90
+ if (firstErrorField_1) {
91
+ inputRef_1 = currentRefs.get(firstErrorField_1);
92
+ setTimeout(function () {
93
+ inputRef_1 === null || inputRef_1 === void 0 ? void 0 : inputRef_1.focus();
94
+ react_native_1.AccessibilityInfo.announceForAccessibility(parsedErrors_1[firstErrorField_1]);
95
+ }, 100);
96
+ }
97
+ }
98
+ return [4 /*yield*/, ((_c = onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit.onError) === null || _c === void 0 ? void 0 : _c.call(onSubmit, parsedErrors_1))];
99
+ case 3: return [2 /*return*/, _e.sent()];
100
+ case 4: return [4 /*yield*/, ((_d = onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit.onFinally) === null || _d === void 0 ? void 0 : _d.call(onSubmit))];
89
101
  case 5:
90
- _d.sent();
102
+ _e.sent();
91
103
  setLoading(false);
92
104
  return [7 /*endfinally*/];
93
105
  case 6: return [2 /*return*/];
@@ -101,7 +113,8 @@ var useForm = function () {
101
113
  clearError: clearError,
102
114
  handleSubmit: handleSubmit,
103
115
  loading: loading || loadingForm,
104
- setLoading: setLoading
116
+ setLoading: setLoading,
117
+ registerInput: context.registerInput
105
118
  };
106
119
  };
107
120
  exports.useForm = useForm;
@@ -160,7 +160,7 @@ var CategorySelect = function (_a) {
160
160
  padding: theme.spacing.md,
161
161
  maxHeight: '90%',
162
162
  minHeight: 300
163
- }} onPress={function (e) { return e.stopPropagation && e.stopPropagation(); }}>
163
+ }} onPress={function (e) { var _a; return (_a = e.stopPropagation) === null || _a === void 0 ? void 0 : _a.call(e); }}>
164
164
  <react_native_1.FlatList data={options} keyExtractor={function (item) { return item.key; }} renderItem={function (_a) {
165
165
  var group = _a.item;
166
166
  return (<react_native_1.View style={{ marginBottom: theme.spacing.xs }}>
@@ -222,11 +222,12 @@ var DeliveryAddressScreen = function (_a) {
222
222
  }
223
223
  });
224
224
  }); };
225
- return (<ScreenWrapper_1.ScreenWrapper header={<Header_1.Header title={t('general.consultation.videoConsultation.title')} leftIcon={<VideoConsultationIcon_1.VideoConsultationIcon />}/>} containerStyle={styles.container}>
225
+ return (<ScreenWrapper_1.ScreenWrapper header={<Header_1.Header title={t('general.consultation.videoConsultation.title')} leftIcon={<VideoConsultationIcon_1.VideoConsultationIcon />}/>} containerStyle={styles.container} scrollEnabled={false}>
226
226
  {error && (<react_native_1.View style={styles.error}>
227
227
  <WarningIcon_1.default />
228
228
  <Text_1.Text style={styles.warningText}>{t('error.address')}</Text_1.Text>
229
229
  </react_native_1.View>)}
230
+
230
231
  <react_native_1.View>
231
232
  <Text_1.Text variant="h2" center>
232
233
  {t('delivery.address.title')}
@@ -234,16 +235,21 @@ var DeliveryAddressScreen = function (_a) {
234
235
  <Text_1.Text center>{t('delivery.address.description')}</Text_1.Text>
235
236
  </react_native_1.View>
236
237
 
237
- <FormWrapper_1.FormWrapper schema={deliveryAddress_1.HKDeliveryAddressSchema} onSubmit={{
238
- onSuccess: onSubmit
238
+ <react_native_1.ScrollView style={styles.formScrollWrapper}>
239
+ <FormWrapper_1.FormWrapper schema={deliveryAddress_1.HKDeliveryAddressSchema} onSubmit={{
240
+ onSuccess: onSubmit,
241
+ onError: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
242
+ return [2 /*return*/, setError(true)];
243
+ }); }); }
239
244
  }} initialValues={initialDeliveryAddress} submitButton={<Button_1.Button submit loading={loading}>
240
- {t('button.confirm')}
241
- </Button_1.Button>} loadingForm={loading}>
242
- <Input_1.default id="house" label={t('delivery.address.number_and_floor')} placeholder={t('delivery.address.number_and_floor.placeholder')}/>
243
- <Input_1.default id="street" label={t('delivery.address.street')} placeholder={t('delivery.address.street.placeholder')}/>
244
- <Input_1.default id="building" label={t('delivery.address.building_name_number')} placeholder={t('delivery.address.building_name_number.placeholder')}/>
245
- <CategorySelect id="district" label={t('delivery.address.district')} placeholder={t('delivery.address.district.placeholder')} options={deliveryAddress_1.DISTRICT_AREAS}/>
246
- </FormWrapper_1.FormWrapper>
245
+ {t('button.confirm')}
246
+ </Button_1.Button>} loadingForm={loading}>
247
+ <Input_1.default id="house" label={t('delivery.address.number_and_floor')} placeholder={t('delivery.address.number_and_floor.placeholder')}/>
248
+ <Input_1.default id="street" label={t('delivery.address.street')} placeholder={t('delivery.address.street.placeholder')}/>
249
+ <Input_1.default id="building" label={t('delivery.address.building_name_number')} placeholder={t('delivery.address.building_name_number.placeholder')}/>
250
+ <CategorySelect id="district" label={t('delivery.address.district')} placeholder={t('delivery.address.district.placeholder')} options={deliveryAddress_1.DISTRICT_AREAS}/>
251
+ </FormWrapper_1.FormWrapper>
252
+ </react_native_1.ScrollView>
247
253
  </ScreenWrapper_1.ScreenWrapper>);
248
254
  };
249
255
  exports.DeliveryAddressScreen = DeliveryAddressScreen;
@@ -265,6 +271,9 @@ var getStyles = function (_a) {
265
271
  warningText: {
266
272
  color: palette.white,
267
273
  marginLeft: spacing.sm
274
+ },
275
+ formScrollWrapper: {
276
+ width: '100%'
268
277
  }
269
278
  });
270
279
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abihealth/goapp-react-native",
3
- "version": "1.45.0",
3
+ "version": "1.45.1",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"