@jobber/components-native 0.95.2-JOB-141866-cab4e3f.3 → 0.95.2-JOB-141866-acfcddc.7
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 +2 -2
- package/dist/src/ContentOverlay/ContentOverlay.js +1 -2
- package/dist/src/Form/Form.js +12 -22
- package/dist/src/InputText/InputText.js +36 -3
- package/dist/src/InputText/context/InputAccessoriesContext.js +2 -0
- package/dist/src/InputText/context/InputAccessoriesProvider.js +6 -1
- package/dist/src/hooks/index.js +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/InputText/context/types.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/ContentOverlay/ContentOverlay.tsx +1 -2
- package/src/Form/Form.tsx +18 -25
- package/src/InputText/InputText.tsx +38 -2
- package/src/InputText/context/InputAccessoriesContext.ts +2 -0
- package/src/InputText/context/InputAccessoriesProvider.tsx +7 -0
- package/src/InputText/context/types.ts +2 -0
- package/src/hooks/index.ts +1 -0
- /package/dist/src/{ContentOverlay/hooks → hooks}/useKeyboardVisibility.js +0 -0
- /package/dist/types/src/{ContentOverlay/hooks → hooks}/useKeyboardVisibility.d.ts +0 -0
- /package/src/{ContentOverlay/hooks → hooks}/useKeyboardVisibility.test.ts +0 -0
- /package/src/{ContentOverlay/hooks → hooks}/useKeyboardVisibility.ts +0 -0
|
@@ -19,5 +19,7 @@ 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;
|
|
22
24
|
}
|
|
23
25
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.95.2-JOB-141866-
|
|
3
|
+
"version": "0.95.2-JOB-141866-acfcddc.7+acfcddc57",
|
|
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": "
|
|
99
|
+
"gitHead": "acfcddc57c91d179ef43e2ed44fd25bd3cae6405"
|
|
100
100
|
}
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
useWindowDimensions,
|
|
19
19
|
} from "react-native";
|
|
20
20
|
import { Portal } from "react-native-portalize";
|
|
21
|
-
import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
|
|
22
21
|
import { useStyles } from "./ContentOverlay.style";
|
|
23
22
|
import { useViewLayoutHeight } from "./hooks/useViewLayoutHeight";
|
|
24
23
|
import type {
|
|
@@ -27,7 +26,7 @@ import type {
|
|
|
27
26
|
ModalBackgroundColor,
|
|
28
27
|
} from "./types";
|
|
29
28
|
import { UNSAFE_WrappedModalize } from "./UNSAFE_WrappedModalize";
|
|
30
|
-
import { useIsScreenReaderEnabled } from "../hooks";
|
|
29
|
+
import { useIsScreenReaderEnabled, useKeyboardVisibility } from "../hooks";
|
|
31
30
|
import { IconButton } from "../IconButton";
|
|
32
31
|
import { Heading } from "../Heading";
|
|
33
32
|
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
|
package/src/Form/Form.tsx
CHANGED
|
@@ -2,7 +2,6 @@ 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";
|
|
6
5
|
import { Keyboard, Platform, View, findNodeHandle } from "react-native";
|
|
7
6
|
import { useStyles } from "./Form.style";
|
|
8
7
|
import { FormErrorBanner } from "./components/FormErrorBanner";
|
|
@@ -27,7 +26,10 @@ import { FormSaveButton } from "./components/FormSaveButton";
|
|
|
27
26
|
import { useSaveButtonPosition } from "./hooks/useSaveButtonPosition";
|
|
28
27
|
import { FormCache } from "./components/FormCache/FormCache";
|
|
29
28
|
import { useAtlantisFormContext } from "./context/AtlantisFormContext";
|
|
30
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
InputAccessoriesProvider,
|
|
31
|
+
useInputAccessoriesContext,
|
|
32
|
+
} from "../InputText";
|
|
31
33
|
import { tokens } from "../utils/design";
|
|
32
34
|
import { ErrorMessageProvider } from "../ErrorMessageWrapper";
|
|
33
35
|
|
|
@@ -71,6 +73,7 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
71
73
|
const { scrollViewRef, bottomViewRef, scrollToTop } = useFormViewRefs();
|
|
72
74
|
const [saveButtonHeight, setSaveButtonHeight] = useState(0);
|
|
73
75
|
const [messageBannerHeight, setMessageBannerHeight] = useState(0);
|
|
76
|
+
const { setIsScrolling } = useInputAccessoriesContext();
|
|
74
77
|
const {
|
|
75
78
|
formMethods,
|
|
76
79
|
handleSubmit,
|
|
@@ -122,9 +125,8 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
122
125
|
|
|
123
126
|
const keyboardProps = Platform.select({
|
|
124
127
|
ios: {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
onKeyboardDidChangeFrame: handleKeyboardDidChangeFrame,
|
|
128
|
+
onKeyboardWillHide: handleKeyboardHide,
|
|
129
|
+
onKeyboardWillShow: handleKeyboardShow,
|
|
128
130
|
},
|
|
129
131
|
android: {
|
|
130
132
|
onKeyboardDidHide: handleKeyboardHide,
|
|
@@ -132,10 +134,6 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
132
134
|
},
|
|
133
135
|
});
|
|
134
136
|
|
|
135
|
-
const onLayout = (event: LayoutChangeEvent) => {
|
|
136
|
-
setMessageBannerHeight(event.nativeEvent.layout.height);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
137
|
const styles = useStyles();
|
|
140
138
|
|
|
141
139
|
const { edgeToEdgeEnabled } = useAtlantisFormContext();
|
|
@@ -179,13 +177,23 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
179
177
|
contentContainerStyle={
|
|
180
178
|
!keyboardHeight && styles.scrollContentContainer
|
|
181
179
|
}
|
|
180
|
+
onScrollBeginDrag={() => {
|
|
181
|
+
setIsScrolling(true);
|
|
182
|
+
}}
|
|
183
|
+
onScrollEndDrag={() => {
|
|
184
|
+
setIsScrolling(false);
|
|
185
|
+
}}
|
|
182
186
|
>
|
|
183
187
|
<View
|
|
184
188
|
onLayout={({ nativeEvent }) => {
|
|
185
189
|
setFormContentHeight(nativeEvent.layout.height);
|
|
186
190
|
}}
|
|
187
191
|
>
|
|
188
|
-
<View
|
|
192
|
+
<View
|
|
193
|
+
onLayout={({ nativeEvent }) => {
|
|
194
|
+
setMessageBannerHeight(nativeEvent.layout.height);
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
189
197
|
<FormMessageBanner bannerMessages={bannerMessages} />
|
|
190
198
|
<FormErrorBanner
|
|
191
199
|
networkError={bannerErrors?.networkError}
|
|
@@ -230,21 +238,6 @@ function InternalForm<T extends FieldValues, S>({
|
|
|
230
238
|
</FormProvider>
|
|
231
239
|
);
|
|
232
240
|
|
|
233
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
|
-
function handleKeyboardDidChangeFrame(frames: Record<string, any>) {
|
|
235
|
-
if (
|
|
236
|
-
frames &&
|
|
237
|
-
"endCoordinates" in frames &&
|
|
238
|
-
"height" in frames.endCoordinates &&
|
|
239
|
-
typeof frames.endCoordinates.height === "number" &&
|
|
240
|
-
frames.endCoordinates.height > keyboardHeight
|
|
241
|
-
) {
|
|
242
|
-
handleKeyboardShow(frames);
|
|
243
|
-
} else {
|
|
244
|
-
handleKeyboardHide();
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
241
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
249
242
|
function handleKeyboardShow(frames: Record<string, any>) {
|
|
250
243
|
setKeyboardScreenY(frames.endCoordinates.screenY);
|
|
@@ -9,6 +9,7 @@ import React, {
|
|
|
9
9
|
} from "react";
|
|
10
10
|
import type {
|
|
11
11
|
FocusEvent,
|
|
12
|
+
LayoutChangeEvent,
|
|
12
13
|
ReturnKeyTypeOptions,
|
|
13
14
|
StyleProp,
|
|
14
15
|
TextInputProps,
|
|
@@ -29,6 +30,15 @@ import type {
|
|
|
29
30
|
} from "../InputFieldWrapper/InputFieldWrapper";
|
|
30
31
|
import { InputFieldWrapper } from "../InputFieldWrapper";
|
|
31
32
|
import { useCommonInputStyles } from "../InputFieldWrapper/CommonInputStyles.style";
|
|
33
|
+
import { useScreenInformation } from "../Form/hooks/useScreenInformation";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Buffer zone in pixels for offscreen detection.
|
|
37
|
+
* This makes the detection more sensitive by marking the component as offscreen
|
|
38
|
+
* even if it's technically still visible but within this buffer distance from the edge.
|
|
39
|
+
*/
|
|
40
|
+
// 44 (accessory bar height) + 20 (buffer)
|
|
41
|
+
const KEYBOARD_AWARE_DETECTION_BUFFER = 64;
|
|
32
42
|
|
|
33
43
|
export interface InputTextProps
|
|
34
44
|
extends Pick<
|
|
@@ -333,6 +343,7 @@ function InputTextInternal(
|
|
|
333
343
|
setFocusedInput,
|
|
334
344
|
canFocusNext,
|
|
335
345
|
onFocusNext,
|
|
346
|
+
isScrolling,
|
|
336
347
|
} = useInputAccessoriesContext();
|
|
337
348
|
useEffect(() => {
|
|
338
349
|
_name &&
|
|
@@ -372,6 +383,10 @@ function InputTextInternal(
|
|
|
372
383
|
|
|
373
384
|
const styles = useStyles();
|
|
374
385
|
const commonInputStyles = useCommonInputStyles();
|
|
386
|
+
const { headerHeight, windowHeight } = useScreenInformation();
|
|
387
|
+
// State to track if the InputText component can fully fit on screen
|
|
388
|
+
// (i.e., it's completely visible). Use this state to handle visibility issues.
|
|
389
|
+
const [canFullyFitOnScreen, setCanFullyFitOnScreen] = useState(true);
|
|
375
390
|
|
|
376
391
|
return (
|
|
377
392
|
<InputFieldWrapper
|
|
@@ -394,6 +409,20 @@ function InputTextInternal(
|
|
|
394
409
|
loadingType={loadingType}
|
|
395
410
|
>
|
|
396
411
|
<TextInput
|
|
412
|
+
onLayout={(event: LayoutChangeEvent) => {
|
|
413
|
+
event.target?.measureInWindow((_, y, __, height) => {
|
|
414
|
+
// Check if component can't fully fit on screen (height only)
|
|
415
|
+
// Account for headerHeight at the top of the screen and buffer zone
|
|
416
|
+
const visibleTop = headerHeight + KEYBOARD_AWARE_DETECTION_BUFFER; // Top of visible area (below header) with buffer
|
|
417
|
+
const visibleBottom =
|
|
418
|
+
windowHeight - KEYBOARD_AWARE_DETECTION_BUFFER; // Bottom of visible area with buffer
|
|
419
|
+
const isOffScreen =
|
|
420
|
+
y < visibleTop || // Top edge is behind or above the header (with buffer)
|
|
421
|
+
y + height > visibleBottom; // Bottom edge is below the window (with buffer)
|
|
422
|
+
|
|
423
|
+
setCanFullyFitOnScreen(!isOffScreen);
|
|
424
|
+
});
|
|
425
|
+
}}
|
|
397
426
|
inputAccessoryViewID={inputAccessoryID || undefined}
|
|
398
427
|
testID={testID}
|
|
399
428
|
autoCapitalize={autoCapitalize}
|
|
@@ -413,14 +442,21 @@ function InputTextInternal(
|
|
|
413
442
|
styleOverride?.inputText,
|
|
414
443
|
loading && loadingType === "glimmer" && { color: "transparent" },
|
|
415
444
|
]}
|
|
416
|
-
|
|
445
|
+
// Prevent focus during scroll for multiline inputs to avoid
|
|
446
|
+
// the input focusing when the user is trying to scroll the form
|
|
447
|
+
readOnly={readonly || (multiline && isScrolling && !focused)}
|
|
448
|
+
// readOnly={readonly}
|
|
417
449
|
editable={!disabled}
|
|
418
450
|
keyboardType={keyboard}
|
|
419
451
|
value={inputTransform(internalValue)}
|
|
420
452
|
autoFocus={autoFocus}
|
|
421
453
|
autoComplete={autoComplete}
|
|
422
454
|
multiline={multiline}
|
|
423
|
-
|
|
455
|
+
// Makes sure it doesn't jump to the top of the screen when the keyboard is shown and a new line is added.
|
|
456
|
+
// State for tracking if the input should be scrollable.
|
|
457
|
+
// This is tech debt related to an issue where keyboard aware scrollview doesn't work if `scrollEnabled` is true. However,
|
|
458
|
+
// 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.
|
|
459
|
+
scrollEnabled={Platform.OS === "ios" && !canFullyFitOnScreen}
|
|
424
460
|
textContentType={textContentType}
|
|
425
461
|
onChangeText={handleChangeText}
|
|
426
462
|
onSubmitEditing={handleOnSubmitEditing}
|
|
@@ -12,6 +12,8 @@ const inputAccessoriesContextDefaultValues: InputAccessoriesContextProps = {
|
|
|
12
12
|
onFocusNext: () => undefined,
|
|
13
13
|
onFocusPrevious: () => undefined,
|
|
14
14
|
setFocusedInput: () => undefined,
|
|
15
|
+
isScrolling: false,
|
|
16
|
+
setIsScrolling: () => undefined,
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export const InputAccessoriesContext = createContext(
|
|
@@ -27,6 +27,8 @@ export function InputAccessoriesProvider({
|
|
|
27
27
|
setElements,
|
|
28
28
|
previousKey,
|
|
29
29
|
nextKey,
|
|
30
|
+
isScrolling,
|
|
31
|
+
setIsScrolling,
|
|
30
32
|
} = useInputAccessoriesProviderState();
|
|
31
33
|
|
|
32
34
|
const colorScheme = useColorScheme();
|
|
@@ -46,6 +48,8 @@ export function InputAccessoriesProvider({
|
|
|
46
48
|
onFocusNext,
|
|
47
49
|
onFocusPrevious,
|
|
48
50
|
setFocusedInput,
|
|
51
|
+
isScrolling,
|
|
52
|
+
setIsScrolling,
|
|
49
53
|
}}
|
|
50
54
|
>
|
|
51
55
|
{children}
|
|
@@ -96,6 +100,7 @@ function useInputAccessoriesProviderState() {
|
|
|
96
100
|
const [canFocusNext, setCanFocusNext] = useState(false);
|
|
97
101
|
const [canFocusPrevious, setCanFocusPrevious] = useState(false);
|
|
98
102
|
const [elements, setElements] = useState<Record<string, () => void>>({});
|
|
103
|
+
const [isScrolling, setIsScrolling] = useState(false);
|
|
99
104
|
|
|
100
105
|
const keys = Object.keys(elements);
|
|
101
106
|
const selectedIndex = keys.findIndex(key => key === focusedInput);
|
|
@@ -119,5 +124,7 @@ function useInputAccessoriesProviderState() {
|
|
|
119
124
|
setCanFocusPrevious,
|
|
120
125
|
elements,
|
|
121
126
|
setElements,
|
|
127
|
+
isScrolling,
|
|
128
|
+
setIsScrolling,
|
|
122
129
|
};
|
|
123
130
|
}
|
|
@@ -25,4 +25,6 @@ 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;
|
|
28
30
|
}
|
package/src/hooks/index.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|