@jobber/components-native 0.95.2-JOB-141866-990fa70.9 → 0.95.2-JOB-141866-a6ee36d.22

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.
@@ -157,6 +157,13 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
157
157
  * Renders a footer below the save button.
158
158
  */
159
159
  renderFooter?: React.ReactNode;
160
+ /**
161
+ * TODO: JOB-147156 This is a HACK for multiline inputs on iOS scrolling issue.
162
+ * Disables the keyboard aware scroll view.
163
+ * This is useful when you want to disable the keyboard aware scroll view for a specific form.
164
+ * For example, when you have a form with a lot of fields and you want to disable the keyboard aware scroll view.
165
+ */
166
+ disableKeyboardAwareScroll?: boolean;
160
167
  }
161
168
  export type InternalFormProps<T extends FieldValues, SubmitResponseType> = Omit<FormProps<T, SubmitResponseType>, "initialLoading">;
162
169
  export type ValidationRulesByFieldPath<T extends FieldValues> = {
@@ -6,11 +6,6 @@ import type { RegisterOptions } from "react-hook-form";
6
6
  import type { IconNames } from "@jobber/design";
7
7
  import type { Clearable } from "@jobber/hooks";
8
8
  import type { InputFieldStyleOverride, InputFieldWrapperProps } from "../InputFieldWrapper/InputFieldWrapper";
9
- /**
10
- * Buffer zone in pixels for offscreen detection.
11
- * This makes the detection more sensitive by marking the component as offscreen
12
- * even if it's technically still visible but within this buffer distance from the edge.
13
- */
14
9
  export interface InputTextProps extends Pick<InputFieldWrapperProps, "toolbar" | "toolbarVisibility" | "loading" | "loadingType"> {
15
10
  /**
16
11
  * Highlights the field red and shows message below (if string) to indicate an error
@@ -19,7 +19,5 @@ export interface InputAccessoriesContextProps {
19
19
  readonly onFocusNext: () => void;
20
20
  readonly onFocusPrevious: () => void;
21
21
  readonly setFocusedInput: (name: string) => void;
22
- readonly isScrolling: boolean;
23
- readonly setIsScrolling: (isScrolling: boolean) => void;
24
22
  }
25
23
  export {};
@@ -1,3 +1,2 @@
1
1
  export * from "./useFormController";
2
2
  export * from "./useIsScreenReaderEnabled";
3
- export * from "./useKeyboardVisibility";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.95.2-JOB-141866-990fa70.9+990fa7081",
3
+ "version": "0.95.2-JOB-141866-a6ee36d.22+a6ee36d10",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -96,5 +96,5 @@
96
96
  "react-native-safe-area-context": "^5.4.0",
97
97
  "react-native-svg": ">=12.0.0"
98
98
  },
99
- "gitHead": "990fa708191391b1d9f973c5eccd4dd8ec532424"
99
+ "gitHead": "a6ee36d10f2c1ba08f20ad6bb9019e274bcee16f"
100
100
  }
@@ -18,6 +18,7 @@ import {
18
18
  useWindowDimensions,
19
19
  } from "react-native";
20
20
  import { Portal } from "react-native-portalize";
21
+ import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
21
22
  import { useStyles } from "./ContentOverlay.style";
22
23
  import { useViewLayoutHeight } from "./hooks/useViewLayoutHeight";
23
24
  import type {
@@ -26,7 +27,7 @@ import type {
26
27
  ModalBackgroundColor,
27
28
  } from "./types";
28
29
  import { UNSAFE_WrappedModalize } from "./UNSAFE_WrappedModalize";
29
- import { useIsScreenReaderEnabled, useKeyboardVisibility } from "../hooks";
30
+ import { useIsScreenReaderEnabled } from "../hooks";
30
31
  import { IconButton } from "../IconButton";
31
32
  import { Heading } from "../Heading";
32
33
  import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
package/src/Form/Form.tsx CHANGED
@@ -1,7 +1,8 @@
1
- import React, { useMemo, useState } from "react";
1
+ import React, { useState } from "react";
2
2
  import type { FieldValues } from "react-hook-form";
3
3
  import { FormProvider } from "react-hook-form";
4
4
  import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
5
+ import type { LayoutChangeEvent } from "react-native";
5
6
  import { Keyboard, Platform, View, findNodeHandle } from "react-native";
6
7
  import { useStyles } from "./Form.style";
7
8
  import { FormErrorBanner } from "./components/FormErrorBanner";
@@ -26,10 +27,7 @@ import { FormSaveButton } from "./components/FormSaveButton";
26
27
  import { useSaveButtonPosition } from "./hooks/useSaveButtonPosition";
27
28
  import { FormCache } from "./components/FormCache/FormCache";
28
29
  import { useAtlantisFormContext } from "./context/AtlantisFormContext";
29
- import {
30
- InputAccessoriesProvider,
31
- useInputAccessoriesContext,
32
- } from "../InputText";
30
+ import { InputAccessoriesProvider } from "../InputText";
33
31
  import { tokens } from "../utils/design";
34
32
  import { ErrorMessageProvider } from "../ErrorMessageWrapper";
35
33
 
@@ -67,13 +65,13 @@ function InternalForm<T extends FieldValues, S>({
67
65
  secondaryActions,
68
66
  saveButtonOffset,
69
67
  showStickySaveButton = false,
68
+ disableKeyboardAwareScroll = false,
70
69
  renderFooter,
71
70
  UNSAFE_allowDiscardLocalCacheWhenOffline,
72
71
  }: InternalFormProps<T, S>) {
73
72
  const { scrollViewRef, bottomViewRef, scrollToTop } = useFormViewRefs();
74
73
  const [saveButtonHeight, setSaveButtonHeight] = useState(0);
75
74
  const [messageBannerHeight, setMessageBannerHeight] = useState(0);
76
- const { setIsScrolling } = useInputAccessoriesContext();
77
75
  const {
78
76
  formMethods,
79
77
  handleSubmit,
@@ -111,12 +109,8 @@ function InternalForm<T extends FieldValues, S>({
111
109
  const [isSecondaryActionLoading, setIsSecondaryActionLoading] =
112
110
  useState<boolean>(false);
113
111
 
114
- const { calculatedKeyboardHeight } = useMemo(() => {
115
- return {
116
- calculatedKeyboardHeight:
117
- keyboardHeight - (paddingBottom + KEYBOARD_SAVE_BUTTON_DISTANCE),
118
- };
119
- }, [paddingBottom, keyboardHeight]);
112
+ const extraViewHeight = paddingBottom + KEYBOARD_SAVE_BUTTON_DISTANCE;
113
+ const calculatedKeyboardHeight = keyboardHeight - extraViewHeight;
120
114
 
121
115
  useScrollToError({
122
116
  formState: formMethods.formState,
@@ -138,6 +132,10 @@ function InternalForm<T extends FieldValues, S>({
138
132
  },
139
133
  });
140
134
 
135
+ const onLayout = (event: LayoutChangeEvent) => {
136
+ setMessageBannerHeight(event.nativeEvent.layout.height);
137
+ };
138
+
141
139
  const styles = useStyles();
142
140
 
143
141
  const { edgeToEdgeEnabled } = useAtlantisFormContext();
@@ -168,7 +166,7 @@ function InternalForm<T extends FieldValues, S>({
168
166
  >
169
167
  <KeyboardAwareScrollView
170
168
  enableResetScrollToCoords={false}
171
- enableAutomaticScroll={true}
169
+ enableAutomaticScroll={!disableKeyboardAwareScroll}
172
170
  enableOnAndroid={edgeToEdgeEnabled}
173
171
  keyboardOpeningTime={
174
172
  Platform.OS === "ios" ? tokens["timing-slowest"] : 0
@@ -181,23 +179,13 @@ function InternalForm<T extends FieldValues, S>({
181
179
  contentContainerStyle={
182
180
  !keyboardHeight && styles.scrollContentContainer
183
181
  }
184
- onScrollBeginDrag={() => {
185
- setIsScrolling(true);
186
- }}
187
- onScrollEndDrag={() => {
188
- setIsScrolling(false);
189
- }}
190
182
  >
191
183
  <View
192
184
  onLayout={({ nativeEvent }) => {
193
185
  setFormContentHeight(nativeEvent.layout.height);
194
186
  }}
195
187
  >
196
- <View
197
- onLayout={({ nativeEvent }) => {
198
- setMessageBannerHeight(nativeEvent.layout.height);
199
- }}
200
- >
188
+ <View onLayout={onLayout}>
201
189
  <FormMessageBanner bannerMessages={bannerMessages} />
202
190
  <FormErrorBanner
203
191
  networkError={bannerErrors?.networkError}
package/src/Form/types.ts CHANGED
@@ -201,6 +201,14 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
201
201
  * Renders a footer below the save button.
202
202
  */
203
203
  renderFooter?: React.ReactNode;
204
+
205
+ /**
206
+ * TODO: JOB-147156 This is a HACK for multiline inputs on iOS scrolling issue.
207
+ * Disables the keyboard aware scroll view.
208
+ * This is useful when you want to disable the keyboard aware scroll view for a specific form.
209
+ * For example, when you have a form with a lot of fields and you want to disable the keyboard aware scroll view.
210
+ */
211
+ disableKeyboardAwareScroll?: boolean;
204
212
  }
205
213
 
206
214
  export type InternalFormProps<T extends FieldValues, SubmitResponseType> = Omit<
@@ -29,15 +29,6 @@ import type {
29
29
  } from "../InputFieldWrapper/InputFieldWrapper";
30
30
  import { InputFieldWrapper } from "../InputFieldWrapper";
31
31
  import { useCommonInputStyles } from "../InputFieldWrapper/CommonInputStyles.style";
32
- // import { useScreenInformation } from "../Form/hooks/useScreenInformation";
33
-
34
- /**
35
- * Buffer zone in pixels for offscreen detection.
36
- * This makes the detection more sensitive by marking the component as offscreen
37
- * even if it's technically still visible but within this buffer distance from the edge.
38
- */
39
- // 44 (accessory bar height) + 20 (buffer)
40
- // const KEYBOARD_AWARE_DETECTION_BUFFER = 64;
41
32
 
42
33
  export interface InputTextProps
43
34
  extends Pick<
@@ -341,7 +332,6 @@ function InputTextInternal(
341
332
  unregister,
342
333
  setFocusedInput,
343
334
  canFocusNext,
344
- isScrolling,
345
335
  onFocusNext,
346
336
  } = useInputAccessoriesContext();
347
337
  useEffect(() => {
@@ -382,10 +372,6 @@ function InputTextInternal(
382
372
 
383
373
  const styles = useStyles();
384
374
  const commonInputStyles = useCommonInputStyles();
385
- // const { headerHeight, windowHeight } = useScreenInformation();
386
- // State to track if the InputText component can fully fit on screen
387
- // (i.e., it's completely visible). Use this state to handle visibility issues.
388
- // const [canFullyFitOnScreen, setCanFullyFitOnScreen] = useState(true);
389
375
 
390
376
  return (
391
377
  <InputFieldWrapper
@@ -408,20 +394,6 @@ function InputTextInternal(
408
394
  loadingType={loadingType}
409
395
  >
410
396
  <TextInput
411
- // onLayout={(event: LayoutChangeEvent) => {
412
- // event.target?.measureInWindow((_, y, __, height) => {
413
- // // Check if component can't fully fit on screen (height only)
414
- // // Account for headerHeight at the top of the screen and buffer zone
415
- // const visibleTop = headerHeight + KEYBOARD_AWARE_DETECTION_BUFFER; // Top of visible area (below header) with buffer
416
- // const visibleBottom =
417
- // windowHeight - KEYBOARD_AWARE_DETECTION_BUFFER; // Bottom of visible area with buffer
418
- // const isOffScreen =
419
- // y < visibleTop || // Top edge is behind or above the header (with buffer)
420
- // y + height > visibleBottom; // Bottom edge is below the window (with buffer)
421
-
422
- // setCanFullyFitOnScreen(!isOffScreen);
423
- // });
424
- // }}
425
397
  inputAccessoryViewID={inputAccessoryID || undefined}
426
398
  testID={testID}
427
399
  autoCapitalize={autoCapitalize}
@@ -441,21 +413,14 @@ function InputTextInternal(
441
413
  styleOverride?.inputText,
442
414
  loading && loadingType === "glimmer" && { color: "transparent" },
443
415
  ]}
444
- // Prevent focus during scroll for multiline inputs to avoid
445
- // the input focusing when the user is trying to scroll the form
446
- readOnly={readonly || (multiline && isScrolling && !focused)}
447
- // readOnly={readonly}
416
+ readOnly={readonly}
448
417
  editable={!disabled}
449
418
  keyboardType={keyboard}
450
419
  value={inputTransform(internalValue)}
451
420
  autoFocus={autoFocus}
452
421
  autoComplete={autoComplete}
453
422
  multiline={multiline}
454
- // Makes sure it doesn't jump to the top of the screen when the keyboard is shown and a new line is added.
455
- // State for tracking if the input should be scrollable.
456
- // This is tech debt related to an issue where keyboard aware scrollview doesn't work if `scrollEnabled` is true. However,
457
- // when `scrollEnabled` is false it causes an issue where super long text inputs will jump to the top when a new line is added to the bottom of the input.
458
- scrollEnabled={Platform.OS === "ios"}
423
+ scrollEnabled={false}
459
424
  textContentType={textContentType}
460
425
  onChangeText={handleChangeText}
461
426
  onSubmitEditing={handleOnSubmitEditing}
@@ -12,8 +12,6 @@ const inputAccessoriesContextDefaultValues: InputAccessoriesContextProps = {
12
12
  onFocusNext: () => undefined,
13
13
  onFocusPrevious: () => undefined,
14
14
  setFocusedInput: () => undefined,
15
- isScrolling: false,
16
- setIsScrolling: () => undefined,
17
15
  };
18
16
 
19
17
  export const InputAccessoriesContext = createContext(
@@ -27,8 +27,6 @@ export function InputAccessoriesProvider({
27
27
  setElements,
28
28
  previousKey,
29
29
  nextKey,
30
- isScrolling,
31
- setIsScrolling,
32
30
  } = useInputAccessoriesProviderState();
33
31
 
34
32
  const colorScheme = useColorScheme();
@@ -48,8 +46,6 @@ export function InputAccessoriesProvider({
48
46
  onFocusNext,
49
47
  onFocusPrevious,
50
48
  setFocusedInput,
51
- isScrolling,
52
- setIsScrolling,
53
49
  }}
54
50
  >
55
51
  {children}
@@ -100,7 +96,6 @@ function useInputAccessoriesProviderState() {
100
96
  const [canFocusNext, setCanFocusNext] = useState(false);
101
97
  const [canFocusPrevious, setCanFocusPrevious] = useState(false);
102
98
  const [elements, setElements] = useState<Record<string, () => void>>({});
103
- const [isScrolling, setIsScrolling] = useState(false);
104
99
 
105
100
  const keys = Object.keys(elements);
106
101
  const selectedIndex = keys.findIndex(key => key === focusedInput);
@@ -124,7 +119,5 @@ function useInputAccessoriesProviderState() {
124
119
  setCanFocusPrevious,
125
120
  elements,
126
121
  setElements,
127
- isScrolling,
128
- setIsScrolling,
129
122
  };
130
123
  }
@@ -25,6 +25,4 @@ export interface InputAccessoriesContextProps {
25
25
  readonly onFocusNext: () => void;
26
26
  readonly onFocusPrevious: () => void;
27
27
  readonly setFocusedInput: (name: string) => void;
28
- readonly isScrolling: boolean;
29
- readonly setIsScrolling: (isScrolling: boolean) => void;
30
28
  }
@@ -1,3 +1,2 @@
1
1
  export * from "./useFormController";
2
2
  export * from "./useIsScreenReaderEnabled";
3
- export * from "./useKeyboardVisibility";