@jobber/components-native 0.95.2-JOB-141866-1ab1d84.4 → 0.95.2-JOB-141866-80fe04c.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.95.2-JOB-141866-1ab1d84.4+1ab1d8407",
3
+ "version": "0.95.2-JOB-141866-80fe04c.8+80fe04c86",
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": "1ab1d8407fce8e7c5ea822477740ec1d5b8f1abb"
99
+ "gitHead": "80fe04c86fc91e07591231460c32bc3194072bdc"
100
100
  }
@@ -2,11 +2,10 @@ import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, u
2
2
  import { useSafeAreaInsets } from "react-native-safe-area-context";
3
3
  import { AccessibilityInfo, Platform, View, findNodeHandle, useWindowDimensions, } from "react-native";
4
4
  import { Portal } from "react-native-portalize";
5
- import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
6
5
  import { useStyles } from "./ContentOverlay.style";
7
6
  import { useViewLayoutHeight } from "./hooks/useViewLayoutHeight";
8
7
  import { UNSAFE_WrappedModalize } from "./UNSAFE_WrappedModalize";
9
- import { useIsScreenReaderEnabled } from "../hooks";
8
+ import { useIsScreenReaderEnabled, useKeyboardVisibility } from "../hooks";
10
9
  import { IconButton } from "../IconButton";
11
10
  import { Heading } from "../Heading";
12
11
  import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
@@ -18,7 +18,7 @@ var __rest = (this && this.__rest) || function (s, e) {
18
18
  }
19
19
  return t;
20
20
  };
21
- import React, { useState } from "react";
21
+ import React, { useMemo, useState } from "react";
22
22
  import { FormProvider } from "react-hook-form";
23
23
  import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
24
24
  import { Keyboard, Platform, View, findNodeHandle } from "react-native";
@@ -54,6 +54,7 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
54
54
  const { scrollViewRef, bottomViewRef, scrollToTop } = useFormViewRefs();
55
55
  const [saveButtonHeight, setSaveButtonHeight] = useState(0);
56
56
  const [messageBannerHeight, setMessageBannerHeight] = useState(0);
57
+ // const { setIsScrolling } = useInputAccessoriesContext();
57
58
  const { formMethods, handleSubmit, isSubmitting, removeListenerRef, setLocalCache, } = useInternalForm({
58
59
  mode,
59
60
  reValidateMode,
@@ -81,8 +82,11 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
81
82
  keyboardScreenY,
82
83
  });
83
84
  const [isSecondaryActionLoading, setIsSecondaryActionLoading] = useState(false);
84
- const extraViewHeight = paddingBottom + KEYBOARD_SAVE_BUTTON_DISTANCE;
85
- const calculatedKeyboardHeight = keyboardHeight - extraViewHeight;
85
+ const { calculatedKeyboardHeight } = useMemo(() => {
86
+ return {
87
+ calculatedKeyboardHeight: keyboardHeight - (paddingBottom + KEYBOARD_SAVE_BUTTON_DISTANCE),
88
+ };
89
+ }, [paddingBottom, keyboardHeight]);
86
90
  useScrollToError({
87
91
  formState: formMethods.formState,
88
92
  refNode: findNodeHandle(scrollViewRef.current),
@@ -92,18 +96,14 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
92
96
  const handleOfflineSubmit = useOfflineHandler();
93
97
  const keyboardProps = Platform.select({
94
98
  ios: {
95
- // onKeyboardDidHide: handleKeyboardHide,
96
- // onKeyboardDidShow: handleKeyboardShow,
97
- onKeyboardDidChangeFrame: handleKeyboardDidChangeFrame,
99
+ onKeyboardWillHide: handleKeyboardHide,
100
+ onKeyboardWillShow: handleKeyboardShow,
98
101
  },
99
102
  android: {
100
103
  onKeyboardDidHide: handleKeyboardHide,
101
104
  onKeyboardDidShow: handleKeyboardShow,
102
105
  },
103
106
  });
104
- const onLayout = (event) => {
105
- setMessageBannerHeight(event.nativeEvent.layout.height);
106
- };
107
107
  const styles = useStyles();
108
108
  const { edgeToEdgeEnabled } = useAtlantisFormContext();
109
109
  return (React.createElement(FormProvider, Object.assign({}, formMethods),
@@ -115,7 +115,9 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
115
115
  React.createElement(View, { onLayout: ({ nativeEvent }) => {
116
116
  setFormContentHeight(nativeEvent.layout.height);
117
117
  } },
118
- React.createElement(View, { onLayout: onLayout },
118
+ React.createElement(View, { onLayout: ({ nativeEvent }) => {
119
+ setMessageBannerHeight(nativeEvent.layout.height);
120
+ } },
119
121
  React.createElement(FormMessageBanner, { bannerMessages: bannerMessages }),
120
122
  React.createElement(FormErrorBanner, { networkError: bannerErrors === null || bannerErrors === void 0 ? void 0 : bannerErrors.networkError, bannerError: bannerErrors === null || bannerErrors === void 0 ? void 0 : bannerErrors.bannerError })),
121
123
  React.createElement(View, { style: styles.formChildContainer },
@@ -126,19 +128,6 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
126
128
  React.createElement(View, { style: styles.safeArea, ref: bottomViewRef })))),
127
129
  React.createElement(FormMessage, null)));
128
130
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
- function handleKeyboardDidChangeFrame(frames) {
130
- if (frames &&
131
- "endCoordinates" in frames &&
132
- "height" in frames.endCoordinates &&
133
- typeof frames.endCoordinates.height === "number" &&
134
- frames.endCoordinates.height > keyboardHeight) {
135
- handleKeyboardShow(frames);
136
- }
137
- else {
138
- handleKeyboardHide();
139
- }
140
- }
141
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
131
  function handleKeyboardShow(frames) {
143
132
  setKeyboardScreenY(frames.endCoordinates.screenY);
144
133
  setKeyboardHeight(frames.endCoordinates.height);
@@ -7,6 +7,13 @@ import { useInputAccessoriesContext } from "./context";
7
7
  import { useFormController } from "../hooks";
8
8
  import { InputFieldWrapper } from "../InputFieldWrapper";
9
9
  import { useCommonInputStyles } from "../InputFieldWrapper/CommonInputStyles.style";
10
+ /**
11
+ * Buffer zone in pixels for offscreen detection.
12
+ * This makes the detection more sensitive by marking the component as offscreen
13
+ * even if it's technically still visible but within this buffer distance from the edge.
14
+ */
15
+ // 44 (accessory bar height) + 20 (buffer)
16
+ const KEYBOARD_AWARE_DETECTION_BUFFER = 64;
10
17
  export const InputText = forwardRef(InputTextInternal);
11
18
  // eslint-disable-next-line max-statements
12
19
  function InputTextInternal({ invalid, disabled, readonly = false, name, placeholder, assistiveText, showMiniLabel = true, keyboard, value: controlledValue, defaultValue, autoFocus, autoComplete = "off", spellCheck, textContentType = "none", validations, onChangeText, onSubmitEditing, onFocus, accessibilityLabel, accessibilityHint, autoCorrect, autoCapitalize, onBlur, multiline = false, prefix, suffix, transform = {}, clearable = multiline ? "never" : "while-editing", testID, secureTextEntry, styleOverride, toolbar, toolbarVisibility, loading, loadingType, }, ref) {
@@ -75,8 +82,40 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
75
82
  }
76
83
  const styles = useStyles();
77
84
  const commonInputStyles = useCommonInputStyles();
85
+ // const { headerHeight, windowHeight } = useScreenInformation();
86
+ // State to track if the InputText component can fully fit on screen
87
+ // (i.e., it's completely visible). Use this state to handle visibility issues.
88
+ // const [canFullyFitOnScreen, setCanFullyFitOnScreen] = useState(true);
78
89
  return (React.createElement(InputFieldWrapper, { prefix: prefix, suffix: suffix, hasValue: hasValue, placeholderMode: placeholderMode, assistiveText: assistiveText, focused: focused, error: error, invalid: invalid, placeholder: placeholder, disabled: disabled, onClear: handleClear, showClearAction: showClear, styleOverride: styleOverride, toolbar: toolbar, toolbarVisibility: toolbarVisibility, loading: loading, loadingType: loadingType },
79
- React.createElement(TextInput, Object.assign({ inputAccessoryViewID: inputAccessoryID || undefined, testID: testID, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, spellCheck: spellCheck, style: [
90
+ React.createElement(TextInput
91
+ // onLayout={(event: LayoutChangeEvent) => {
92
+ // event.target?.measureInWindow((_, y, __, height) => {
93
+ // // Check if component can't fully fit on screen (height only)
94
+ // // Account for headerHeight at the top of the screen and buffer zone
95
+ // const visibleTop = headerHeight + KEYBOARD_AWARE_DETECTION_BUFFER; // Top of visible area (below header) with buffer
96
+ // const visibleBottom =
97
+ // windowHeight - KEYBOARD_AWARE_DETECTION_BUFFER; // Bottom of visible area with buffer
98
+ // const isOffScreen =
99
+ // y < visibleTop || // Top edge is behind or above the header (with buffer)
100
+ // y + height > visibleBottom; // Bottom edge is below the window (with buffer)
101
+ // setCanFullyFitOnScreen(!isOffScreen);
102
+ // });
103
+ // }}
104
+ , Object.assign({
105
+ // onLayout={(event: LayoutChangeEvent) => {
106
+ // event.target?.measureInWindow((_, y, __, height) => {
107
+ // // Check if component can't fully fit on screen (height only)
108
+ // // Account for headerHeight at the top of the screen and buffer zone
109
+ // const visibleTop = headerHeight + KEYBOARD_AWARE_DETECTION_BUFFER; // Top of visible area (below header) with buffer
110
+ // const visibleBottom =
111
+ // windowHeight - KEYBOARD_AWARE_DETECTION_BUFFER; // Bottom of visible area with buffer
112
+ // const isOffScreen =
113
+ // y < visibleTop || // Top edge is behind or above the header (with buffer)
114
+ // y + height > visibleBottom; // Bottom edge is below the window (with buffer)
115
+ // setCanFullyFitOnScreen(!isOffScreen);
116
+ // });
117
+ // }}
118
+ inputAccessoryViewID: inputAccessoryID || undefined, testID: testID, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, spellCheck: spellCheck, style: [
80
119
  commonInputStyles.input,
81
120
  styles.inputPaddingTop,
82
121
  !miniLabelActive && commonInputStyles.inputEmpty,
@@ -89,7 +128,16 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
89
128
  styles.multilineWithoutMiniLabel,
90
129
  styleOverride === null || styleOverride === void 0 ? void 0 : styleOverride.inputText,
91
130
  loading && loadingType === "glimmer" && { color: "transparent" },
92
- ], readOnly: readonly, editable: !disabled, keyboardType: keyboard, value: inputTransform(internalValue), autoFocus: autoFocus, autoComplete: autoComplete, multiline: multiline, scrollEnabled: false, textContentType: textContentType, onChangeText: handleChangeText, onSubmitEditing: handleOnSubmitEditing, returnKeyType: returnKeyType, blurOnSubmit: shouldBlurOnSubmit, accessibilityLabel: accessibilityLabel || placeholder, accessibilityHint: accessibilityHint, accessibilityState: { busy: loading }, secureTextEntry: secureTextEntry }, androidA11yProps, { onFocus: event => {
131
+ ],
132
+ // Prevent focus during scroll for multiline inputs to avoid
133
+ // the input focusing when the user is trying to scroll the form
134
+ // readOnly={readonly || (multiline && isScrolling && !focused)}
135
+ readOnly: readonly, editable: !disabled, keyboardType: keyboard, value: inputTransform(internalValue), autoFocus: autoFocus, autoComplete: autoComplete, multiline: multiline,
136
+ // Makes sure it doesn't jump to the top of the screen when the keyboard is shown and a new line is added.
137
+ // State for tracking if the input should be scrollable.
138
+ // This is tech debt related to an issue where keyboard aware scrollview doesn't work if `scrollEnabled` is true. However,
139
+ // 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.
140
+ scrollEnabled: Platform.OS === "ios", textContentType: textContentType, onChangeText: handleChangeText, onSubmitEditing: handleOnSubmitEditing, returnKeyType: returnKeyType, blurOnSubmit: shouldBlurOnSubmit, accessibilityLabel: accessibilityLabel || placeholder, accessibilityHint: accessibilityHint, accessibilityState: { busy: loading }, secureTextEntry: secureTextEntry }, androidA11yProps, { onFocus: event => {
93
141
  _name && setFocusedInput(_name);
94
142
  setFocused(true);
95
143
  onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
@@ -10,6 +10,8 @@ const inputAccessoriesContextDefaultValues = {
10
10
  onFocusNext: () => undefined,
11
11
  onFocusPrevious: () => undefined,
12
12
  setFocusedInput: () => undefined,
13
+ isScrolling: false,
14
+ setIsScrolling: () => undefined,
13
15
  };
14
16
  export const InputAccessoriesContext = createContext(inputAccessoriesContextDefaultValues);
15
17
  export function useInputAccessoriesContext() {
@@ -5,7 +5,7 @@ import { InputAccessoriesContext } from "./InputAccessoriesContext";
5
5
  import { useStyles } from "./InputAccessoriesProvider.style";
6
6
  export function InputAccessoriesProvider({ children, }) {
7
7
  const inputAccessoryID = useRef(v4()).current;
8
- const { focusedInput, setFocusedInput, canFocusNext, canFocusPrevious, elements, setElements, previousKey, nextKey, } = useInputAccessoriesProviderState();
8
+ const { focusedInput, setFocusedInput, canFocusNext, canFocusPrevious, elements, setElements, previousKey, nextKey, isScrolling, setIsScrolling, } = useInputAccessoriesProviderState();
9
9
  const colorScheme = useColorScheme();
10
10
  const styles = useStyles();
11
11
  return (React.createElement(InputAccessoriesContext.Provider, { value: {
@@ -19,6 +19,8 @@ export function InputAccessoriesProvider({ children, }) {
19
19
  onFocusNext,
20
20
  onFocusPrevious,
21
21
  setFocusedInput,
22
+ isScrolling,
23
+ setIsScrolling,
22
24
  } },
23
25
  children,
24
26
  Platform.OS === "ios" && (React.createElement(InputAccessoryView, { nativeID: inputAccessoryID },
@@ -49,6 +51,7 @@ function useInputAccessoriesProviderState() {
49
51
  const [canFocusNext, setCanFocusNext] = useState(false);
50
52
  const [canFocusPrevious, setCanFocusPrevious] = useState(false);
51
53
  const [elements, setElements] = useState({});
54
+ const [isScrolling, setIsScrolling] = useState(false);
52
55
  const keys = Object.keys(elements);
53
56
  const selectedIndex = keys.findIndex(key => key === focusedInput);
54
57
  const nextKey = keys[selectedIndex + 1];
@@ -68,5 +71,7 @@ function useInputAccessoriesProviderState() {
68
71
  setCanFocusPrevious,
69
72
  elements,
70
73
  setElements,
74
+ isScrolling,
75
+ setIsScrolling,
71
76
  };
72
77
  }
@@ -1,2 +1,3 @@
1
1
  export * from "./useFormController";
2
2
  export * from "./useIsScreenReaderEnabled";
3
+ export * from "./useKeyboardVisibility";