react-native-keyboard-controller 1.20.7 → 1.21.0-beta.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/android/.settings/org.eclipse.buildship.core.prefs +2 -0
- package/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +2 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +30 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/ClippingScrollViewDecoratorViewManagerImpl.kt +13 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +37 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +24 -0
- package/ios/KeyboardController.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/ios/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcuserdata/kirylziusko.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Tests/Tests.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +32 -0
- package/ios/animations/KeyboardAnimation.swift +2 -2
- package/ios/animations/SpringAnimation.swift +1 -1
- package/ios/animations/TimingAnimation.swift +1 -3
- package/ios/delegates/KCTextInputCompositeDelegate.swift +2 -2
- package/ios/extensions/CGFloat.swift +1 -3
- package/ios/interactive/KeyboardAreaExtender.swift +2 -1
- package/ios/observers/FocusedInputObserver.swift +4 -2
- package/ios/observers/movement/KeyboardTrackingView.swift +4 -1
- package/ios/observers/movement/observer/KeyboardMovementObserver+Interactive.swift +6 -0
- package/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift +30 -7
- package/ios/observers/movement/observer/KeyboardMovementObserver.swift +2 -48
- package/ios/protocols/TextInput.swift +1 -1
- package/ios/swizzling/UIResponderSwizzle.swift +2 -4
- package/ios/traversal/FocusedInputHolder.swift +3 -3
- package/ios/traversal/KeyboardView.swift +1 -1
- package/ios/traversal/ViewHierarchyNavigator.swift +2 -2
- package/ios/views/KeyboardControllerViewManager.swift +5 -5
- package/lib/commonjs/bindings.js +7 -1
- package/lib/commonjs/bindings.js.map +1 -1
- package/lib/commonjs/bindings.native.js +4 -1
- package/lib/commonjs/bindings.native.js.map +1 -1
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +77 -22
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +43 -0
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js.map +1 -0
- package/lib/commonjs/components/ScrollViewWithBottomPadding/styles.js +14 -0
- package/lib/commonjs/components/ScrollViewWithBottomPadding/styles.js.map +1 -0
- package/lib/commonjs/components/hooks/useScrollState.js +43 -0
- package/lib/commonjs/components/hooks/useScrollState.js.map +1 -0
- package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js +12 -0
- package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -0
- package/lib/commonjs/types/views.js.map +1 -1
- package/lib/module/bindings.js +6 -0
- package/lib/module/bindings.js.map +1 -1
- package/lib/module/bindings.native.js +3 -0
- package/lib/module/bindings.native.js.map +1 -1
- package/lib/module/components/KeyboardAwareScrollView/index.js +78 -23
- package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/module/components/ScrollViewWithBottomPadding/index.js +35 -0
- package/lib/module/components/ScrollViewWithBottomPadding/index.js.map +1 -0
- package/lib/module/components/ScrollViewWithBottomPadding/styles.js +8 -0
- package/lib/module/components/ScrollViewWithBottomPadding/styles.js.map +1 -0
- package/lib/module/components/hooks/useScrollState.js +37 -0
- package/lib/module/components/hooks/useScrollState.js.map +1 -0
- package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js +6 -0
- package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -0
- package/lib/module/types/views.js.map +1 -1
- package/lib/typescript/bindings.d.ts +7 -1
- package/lib/typescript/bindings.native.d.ts +1 -0
- package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +3 -2
- package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +12 -0
- package/lib/typescript/components/ScrollViewWithBottomPadding/styles.d.ts +6 -0
- package/lib/typescript/components/hooks/useScrollState.d.ts +14 -0
- package/lib/typescript/specs/ClippingScrollViewDecoratorViewNativeComponent.d.ts +8 -0
- package/lib/typescript/types/views.d.ts +4 -0
- package/package.json +2 -2
- package/src/bindings.native.ts +5 -0
- package/src/bindings.ts +8 -0
- package/src/components/KeyboardAwareScrollView/index.tsx +111 -28
- package/src/components/ScrollViewWithBottomPadding/index.tsx +72 -0
- package/src/components/ScrollViewWithBottomPadding/styles.ts +9 -0
- package/src/components/hooks/useScrollState.ts +59 -0
- package/src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts +17 -0
- package/src/types/views.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-keyboard-controller",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0-beta.1",
|
|
4
4
|
"description": "Keyboard manager which works in identical way on both iOS and Android",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"typescript": "tsc --noEmit --project tsconfig.build.json",
|
|
42
42
|
"lint": "eslint --quiet \"**/*.{js,ts,tsx}\"",
|
|
43
43
|
"lint-clang": "find ios/ -iname *.h -o -iname *.m -o -iname *.mm | grep -v -e Pods -e build | xargs clang-format -i -n --Werror",
|
|
44
|
-
"prepare": "bob build
|
|
44
|
+
"prepare": "bob build",
|
|
45
45
|
"release": "release-it",
|
|
46
46
|
"example": "yarn --cwd example",
|
|
47
47
|
"pods": "cd example && pod-install --quiet",
|
package/src/bindings.native.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NativeEventEmitter, Platform } from "react-native";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ClippingScrollViewProps,
|
|
4
5
|
FocusedInputEventsModule,
|
|
5
6
|
KeyboardBackgroundViewProps,
|
|
6
7
|
KeyboardControllerNativeModule,
|
|
@@ -71,3 +72,7 @@ export const RCTKeyboardExtender: React.FC<KeyboardExtenderProps> =
|
|
|
71
72
|
Platform.OS === "ios"
|
|
72
73
|
? require("./specs/KeyboardExtenderNativeComponent").default
|
|
73
74
|
: ({ children }: KeyboardExtenderProps) => children;
|
|
75
|
+
export const ClippingScrollView: React.FC<KeyboardBackgroundViewProps> =
|
|
76
|
+
Platform.OS === "android"
|
|
77
|
+
? require("./specs/ClippingScrollViewDecoratorViewNativeComponent").default
|
|
78
|
+
: ({ children }: ClippingScrollViewProps) => children;
|
package/src/bindings.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { View } from "react-native";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ClippingScrollViewProps,
|
|
4
5
|
FocusedInputEventsModule,
|
|
5
6
|
KeyboardBackgroundViewProps,
|
|
6
7
|
KeyboardControllerNativeModule,
|
|
@@ -82,3 +83,10 @@ export const KeyboardBackgroundView =
|
|
|
82
83
|
*/
|
|
83
84
|
export const RCTKeyboardExtender =
|
|
84
85
|
View as unknown as React.FC<KeyboardExtenderProps>;
|
|
86
|
+
/**
|
|
87
|
+
* A decorator that will clip the content of the `ScrollView`. It helps to simulate `contentInset` behavior on Android.
|
|
88
|
+
* Supports only `bottom` property (`paddingBottom` is not supported property of `ScrollView.style`).
|
|
89
|
+
* Using this component we can modify bottom inset without having a fake view.
|
|
90
|
+
*/
|
|
91
|
+
export const ClippingScrollView =
|
|
92
|
+
View as unknown as React.FC<ClippingScrollViewProps>;
|
|
@@ -12,8 +12,7 @@ import Reanimated, {
|
|
|
12
12
|
scrollTo,
|
|
13
13
|
useAnimatedReaction,
|
|
14
14
|
useAnimatedRef,
|
|
15
|
-
|
|
16
|
-
useScrollViewOffset,
|
|
15
|
+
useDerivedValue,
|
|
17
16
|
useSharedValue,
|
|
18
17
|
} from "react-native-reanimated";
|
|
19
18
|
|
|
@@ -24,10 +23,13 @@ import {
|
|
|
24
23
|
} from "../../hooks";
|
|
25
24
|
import { findNodeHandle } from "../../utils/findNodeHandle";
|
|
26
25
|
import useCombinedRef from "../hooks/useCombinedRef";
|
|
26
|
+
import useScrollState from "../hooks/useScrollState";
|
|
27
|
+
import ScrollViewWithBottomPadding from "../ScrollViewWithBottomPadding";
|
|
27
28
|
|
|
28
29
|
import { useSmoothKeyboardHandler } from "./useSmoothKeyboardHandler";
|
|
29
30
|
import { debounce, scrollDistanceWithRespectToSnapPoints } from "./utils";
|
|
30
31
|
|
|
32
|
+
import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
|
|
31
33
|
import type {
|
|
32
34
|
LayoutChangeEvent,
|
|
33
35
|
ScrollView,
|
|
@@ -49,7 +51,7 @@ export type KeyboardAwareScrollViewProps = {
|
|
|
49
51
|
/** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
|
|
50
52
|
extraKeyboardSpace?: number;
|
|
51
53
|
/** Custom component for `ScrollView`. Default is `ScrollView`. */
|
|
52
|
-
ScrollViewComponent?:
|
|
54
|
+
ScrollViewComponent?: AnimatedScrollViewComponent;
|
|
53
55
|
} & ScrollViewProps;
|
|
54
56
|
export type KeyboardAwareScrollViewRef = {
|
|
55
57
|
assureFocusedInputVisible: () => void;
|
|
@@ -131,7 +133,11 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
131
133
|
const onRef = useCombinedRef(scrollViewAnimatedRef, scrollViewRef);
|
|
132
134
|
const scrollViewTarget = useSharedValue<number | null>(null);
|
|
133
135
|
const scrollPosition = useSharedValue(0);
|
|
134
|
-
const
|
|
136
|
+
const {
|
|
137
|
+
offset: position,
|
|
138
|
+
layout: scrollViewLayout,
|
|
139
|
+
size: scrollViewContentSize,
|
|
140
|
+
} = useScrollState(scrollViewAnimatedRef);
|
|
135
141
|
const currentKeyboardFrameHeight = useSharedValue(0);
|
|
136
142
|
const keyboardHeight = useSharedValue(0);
|
|
137
143
|
const keyboardWillAppear = useSharedValue(false);
|
|
@@ -142,6 +148,8 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
142
148
|
const layout = useSharedValue<FocusedInputLayoutChangedEvent | null>(null);
|
|
143
149
|
const lastSelection =
|
|
144
150
|
useSharedValue<FocusedInputSelectionChangedEvent | null>(null);
|
|
151
|
+
const ghostViewSpace = useSharedValue(-1);
|
|
152
|
+
const pendingSelectionForFocus = useSharedValue(false);
|
|
145
153
|
|
|
146
154
|
const { height } = useWindowDimensions();
|
|
147
155
|
|
|
@@ -213,6 +221,30 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
213
221
|
},
|
|
214
222
|
[bottomOffset, enabled, height, snapToOffsets],
|
|
215
223
|
);
|
|
224
|
+
const removeGhostPadding = useCallback((e: number) => {
|
|
225
|
+
"worklet";
|
|
226
|
+
|
|
227
|
+
// new `ScrollViewWithBottomPadding` behavior: if we hide keyboard and we are in the end of `ScrollView`
|
|
228
|
+
// then we always need to scroll back, because we apply a padding that doesn't change layout, so we will
|
|
229
|
+
// not have auto scroll back in this case
|
|
230
|
+
if (!keyboardWillAppear.value && ghostViewSpace.value > 0) {
|
|
231
|
+
scrollTo(
|
|
232
|
+
scrollViewAnimatedRef,
|
|
233
|
+
0,
|
|
234
|
+
scrollPosition.value -
|
|
235
|
+
interpolate(
|
|
236
|
+
e,
|
|
237
|
+
[initialKeyboardSize.value, keyboardHeight.value],
|
|
238
|
+
[ghostViewSpace.value, 0],
|
|
239
|
+
),
|
|
240
|
+
false,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return false;
|
|
247
|
+
}, []);
|
|
216
248
|
const performScrollWithPositionRestoration = useCallback(
|
|
217
249
|
(newPosition: number) => {
|
|
218
250
|
"worklet";
|
|
@@ -293,7 +325,18 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
293
325
|
lastSelection.value = e;
|
|
294
326
|
|
|
295
327
|
if (e.target !== lastTarget) {
|
|
296
|
-
|
|
328
|
+
if (pendingSelectionForFocus.value) {
|
|
329
|
+
// selection arrived after onStart - complete the deferred setup
|
|
330
|
+
pendingSelectionForFocus.value = false;
|
|
331
|
+
updateLayoutFromSelection();
|
|
332
|
+
|
|
333
|
+
// if keyboard was already visible (focus change, no onMove expected),
|
|
334
|
+
// perform the deferred scroll now
|
|
335
|
+
if (!keyboardWillAppear.value && keyboardHeight.value > 0) {
|
|
336
|
+
position.value += maybeScroll(keyboardHeight.value, true);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
297
340
|
return;
|
|
298
341
|
}
|
|
299
342
|
// caret in the end + end coordinates has been changed -> we moved to a new line
|
|
@@ -311,7 +354,12 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
311
354
|
|
|
312
355
|
onChangeTextHandler();
|
|
313
356
|
},
|
|
314
|
-
[
|
|
357
|
+
[
|
|
358
|
+
scrollFromCurrentPosition,
|
|
359
|
+
onChangeTextHandler,
|
|
360
|
+
updateLayoutFromSelection,
|
|
361
|
+
maybeScroll,
|
|
362
|
+
],
|
|
315
363
|
);
|
|
316
364
|
|
|
317
365
|
useFocusedInputHandler(
|
|
@@ -344,6 +392,7 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
344
392
|
// on back transition need to interpolate as [0, keyboardHeight]
|
|
345
393
|
initialKeyboardSize.value = 0;
|
|
346
394
|
scrollPosition.value = scrollBeforeKeyboardMovement.value;
|
|
395
|
+
pendingSelectionForFocus.value = false;
|
|
347
396
|
}
|
|
348
397
|
|
|
349
398
|
if (
|
|
@@ -360,17 +409,40 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
360
409
|
// focus was changed
|
|
361
410
|
if (focusWasChanged) {
|
|
362
411
|
tag.value = e.target;
|
|
363
|
-
|
|
364
|
-
|
|
412
|
+
|
|
413
|
+
if (lastSelection.value?.target === e.target) {
|
|
414
|
+
// selection arrived before onStart - use it to update layout
|
|
415
|
+
updateLayoutFromSelection();
|
|
416
|
+
pendingSelectionForFocus.value = false;
|
|
417
|
+
} else {
|
|
418
|
+
// selection hasn't arrived yet for the new target.
|
|
419
|
+
// use input layout as-is; will be refined when selection arrives.
|
|
420
|
+
if (input.value) {
|
|
421
|
+
layout.value = input.value;
|
|
422
|
+
}
|
|
423
|
+
pendingSelectionForFocus.value = true;
|
|
424
|
+
}
|
|
425
|
+
|
|
365
426
|
// save current scroll position - when keyboard will hide we'll reuse
|
|
366
427
|
// this value to achieve smooth hide effect
|
|
367
428
|
scrollBeforeKeyboardMovement.value = position.value;
|
|
368
429
|
}
|
|
369
430
|
|
|
370
431
|
if (focusWasChanged && !keyboardWillAppear.value) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
432
|
+
if (!pendingSelectionForFocus.value) {
|
|
433
|
+
// update position on scroll value, so `onEnd` handler
|
|
434
|
+
// will pick up correct values
|
|
435
|
+
position.value += maybeScroll(e.height, true);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
ghostViewSpace.value =
|
|
440
|
+
position.value +
|
|
441
|
+
scrollViewLayout.value.height -
|
|
442
|
+
scrollViewContentSize.value.height;
|
|
443
|
+
|
|
444
|
+
if (ghostViewSpace.value > 0) {
|
|
445
|
+
scrollPosition.value = position.value;
|
|
374
446
|
}
|
|
375
447
|
},
|
|
376
448
|
onMove: (e) => {
|
|
@@ -378,6 +450,10 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
378
450
|
|
|
379
451
|
syncKeyboardFrame(e);
|
|
380
452
|
|
|
453
|
+
if (removeGhostPadding(e.height)) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
381
457
|
// if the user has set disableScrollOnKeyboardHide, only auto-scroll when the keyboard opens
|
|
382
458
|
if (!disableScrollOnKeyboardHide || keyboardWillAppear.value) {
|
|
383
459
|
maybeScroll(e.height);
|
|
@@ -386,13 +462,24 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
386
462
|
onEnd: (e) => {
|
|
387
463
|
"worklet";
|
|
388
464
|
|
|
465
|
+
removeGhostPadding(e.height);
|
|
466
|
+
|
|
389
467
|
keyboardHeight.value = e.height;
|
|
390
468
|
scrollPosition.value = position.value;
|
|
391
469
|
|
|
470
|
+
if (e.height === 0) {
|
|
471
|
+
lastSelection.value = null;
|
|
472
|
+
}
|
|
473
|
+
|
|
392
474
|
syncKeyboardFrame(e);
|
|
393
475
|
},
|
|
394
476
|
},
|
|
395
|
-
[
|
|
477
|
+
[
|
|
478
|
+
maybeScroll,
|
|
479
|
+
removeGhostPadding,
|
|
480
|
+
disableScrollOnKeyboardHide,
|
|
481
|
+
syncKeyboardFrame,
|
|
482
|
+
],
|
|
396
483
|
);
|
|
397
484
|
|
|
398
485
|
const synchronize = useCallback(async () => {
|
|
@@ -443,32 +530,28 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
443
530
|
[],
|
|
444
531
|
);
|
|
445
532
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
// re-calculation on every animation frame and it helps to achieve smooth animation.
|
|
455
|
-
// see: https://github.com/kirillzyusko/react-native-keyboard-controller/pull/342
|
|
456
|
-
paddingBottom: currentKeyboardFrameHeight.value + 1,
|
|
457
|
-
}
|
|
458
|
-
: {},
|
|
533
|
+
// animations become choppy when scrolling to the end of the `ScrollView` (when the last input is focused)
|
|
534
|
+
// this happens because the layout recalculates on every frame. To avoid this we slightly increase padding
|
|
535
|
+
// by `+1`. In this way we assure, that `scrollTo` will never scroll to the end, because it uses interpolation
|
|
536
|
+
// from 0 to `keyboardHeight`, and here our padding is `keyboardHeight + 1`. It allows us not to re-run layout
|
|
537
|
+
// re-calculation on every animation frame and it helps to achieve smooth animation.
|
|
538
|
+
// see: https://github.com/kirillzyusko/react-native-keyboard-controller/pull/342
|
|
539
|
+
const padding = useDerivedValue(
|
|
540
|
+
() => (enabled ? currentKeyboardFrameHeight.value + 1 : 0),
|
|
459
541
|
[enabled],
|
|
460
542
|
);
|
|
461
543
|
|
|
462
544
|
return (
|
|
463
|
-
<
|
|
545
|
+
<ScrollViewWithBottomPadding
|
|
464
546
|
ref={onRef}
|
|
465
547
|
{...rest}
|
|
548
|
+
bottomPadding={padding}
|
|
466
549
|
scrollEventThrottle={16}
|
|
550
|
+
ScrollViewComponent={ScrollViewComponent}
|
|
467
551
|
onLayout={onScrollViewLayout}
|
|
468
552
|
>
|
|
469
553
|
{children}
|
|
470
|
-
|
|
471
|
-
</ScrollViewComponent>
|
|
554
|
+
</ScrollViewWithBottomPadding>
|
|
472
555
|
);
|
|
473
556
|
},
|
|
474
557
|
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import Reanimated, { useAnimatedProps } from "react-native-reanimated";
|
|
4
|
+
|
|
5
|
+
import { ClippingScrollView } from "../../bindings";
|
|
6
|
+
|
|
7
|
+
import styles from "./styles";
|
|
8
|
+
|
|
9
|
+
import type { ScrollViewProps } from "react-native";
|
|
10
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
11
|
+
|
|
12
|
+
const ReanimatedClippingScrollView =
|
|
13
|
+
Platform.OS === "android"
|
|
14
|
+
? Reanimated.createAnimatedComponent(ClippingScrollView)
|
|
15
|
+
: ClippingScrollView;
|
|
16
|
+
|
|
17
|
+
type AnimatedScrollViewProps = React.ComponentProps<
|
|
18
|
+
typeof Reanimated.ScrollView
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export type AnimatedScrollViewComponent = React.ForwardRefExoticComponent<
|
|
22
|
+
AnimatedScrollViewProps & React.RefAttributes<Reanimated.ScrollView>
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
type ScrollViewWithBottomPaddingProps = {
|
|
26
|
+
ScrollViewComponent: AnimatedScrollViewComponent;
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
bottomPadding: SharedValue<number>;
|
|
29
|
+
} & ScrollViewProps;
|
|
30
|
+
|
|
31
|
+
const ScrollViewWithBottomPadding = forwardRef<
|
|
32
|
+
Reanimated.ScrollView,
|
|
33
|
+
ScrollViewWithBottomPaddingProps
|
|
34
|
+
>(
|
|
35
|
+
(
|
|
36
|
+
{ ScrollViewComponent, bottomPadding, contentInset, children, ...rest },
|
|
37
|
+
ref,
|
|
38
|
+
) => {
|
|
39
|
+
const animatedProps = useAnimatedProps(
|
|
40
|
+
() => ({
|
|
41
|
+
// iOS prop
|
|
42
|
+
contentInset: {
|
|
43
|
+
bottom: bottomPadding.value + (contentInset?.bottom || 0),
|
|
44
|
+
top: contentInset?.top,
|
|
45
|
+
right: contentInset?.right,
|
|
46
|
+
left: contentInset?.left,
|
|
47
|
+
},
|
|
48
|
+
// Android prop
|
|
49
|
+
contentInsetBottom: bottomPadding.value,
|
|
50
|
+
}),
|
|
51
|
+
[
|
|
52
|
+
contentInset?.bottom,
|
|
53
|
+
contentInset?.top,
|
|
54
|
+
contentInset?.right,
|
|
55
|
+
contentInset?.left,
|
|
56
|
+
],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ReanimatedClippingScrollView
|
|
61
|
+
animatedProps={animatedProps}
|
|
62
|
+
style={styles.container}
|
|
63
|
+
>
|
|
64
|
+
<ScrollViewComponent ref={ref} animatedProps={animatedProps} {...rest}>
|
|
65
|
+
{children}
|
|
66
|
+
</ScrollViewComponent>
|
|
67
|
+
</ReanimatedClippingScrollView>
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export default ScrollViewWithBottomPadding;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useEvent, useSharedValue } from "react-native-reanimated";
|
|
3
|
+
|
|
4
|
+
import { useEventHandlerRegistration } from "../../internal";
|
|
5
|
+
|
|
6
|
+
import type { AnimatedRef } from "react-native-reanimated";
|
|
7
|
+
import type Reanimated from "react-native-reanimated";
|
|
8
|
+
|
|
9
|
+
const NATIVE_SCROLL_EVENT_NAMES = [
|
|
10
|
+
"onScroll",
|
|
11
|
+
"onScrollBeginDrag",
|
|
12
|
+
"onScrollEndDrag",
|
|
13
|
+
"onMomentumScrollBegin",
|
|
14
|
+
"onMomentumScrollEnd",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
type ScrollEvent = {
|
|
18
|
+
contentOffset: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
};
|
|
22
|
+
layoutMeasurement: {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
};
|
|
26
|
+
contentSize: {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const useScrollState = (ref: AnimatedRef<Reanimated.ScrollView>) => {
|
|
33
|
+
const offset = useSharedValue(0);
|
|
34
|
+
const layout = useSharedValue({ width: 0, height: 0 });
|
|
35
|
+
const size = useSharedValue({ width: 0, height: 0 });
|
|
36
|
+
|
|
37
|
+
const register = useEventHandlerRegistration(ref);
|
|
38
|
+
|
|
39
|
+
const eventHandler = useEvent((event: ScrollEvent) => {
|
|
40
|
+
"worklet";
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
43
|
+
offset.value = event.contentOffset.y;
|
|
44
|
+
layout.value = event.layoutMeasurement;
|
|
45
|
+
size.value = event.contentSize;
|
|
46
|
+
}, NATIVE_SCROLL_EVENT_NAMES);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const cleanup = register(eventHandler);
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
cleanup();
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
return { offset, layout, size };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default useScrollState;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { codegenNativeComponent } from "react-native";
|
|
2
|
+
|
|
3
|
+
import type { HostComponent } from "react-native";
|
|
4
|
+
import type { ViewProps } from "react-native";
|
|
5
|
+
import type { Double } from "react-native/Libraries/Types/CodegenTypes";
|
|
6
|
+
|
|
7
|
+
export interface NativeProps extends ViewProps {
|
|
8
|
+
contentInsetBottom: Double;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default codegenNativeComponent<NativeProps>(
|
|
12
|
+
"ClippingScrollViewDecoratorView",
|
|
13
|
+
{
|
|
14
|
+
interfaceOnly: true,
|
|
15
|
+
excludedPlatforms: ["iOS"],
|
|
16
|
+
},
|
|
17
|
+
) as HostComponent<NativeProps>;
|
package/src/types/views.ts
CHANGED
|
@@ -49,3 +49,9 @@ export type KeyboardExtenderProps = PropsWithChildren<{
|
|
|
49
49
|
/** Controls whether this `KeyboardExtender` instance should take an effect. Default is `true`. */
|
|
50
50
|
enabled?: boolean;
|
|
51
51
|
}>;
|
|
52
|
+
export type ClippingScrollViewProps = PropsWithChildren<
|
|
53
|
+
ViewProps & {
|
|
54
|
+
/** An additional space that gets applied to the bottom of the `ScrollView` (inside a scrollable content). Default is `0`. */
|
|
55
|
+
contentInsetBottom?: number;
|
|
56
|
+
}
|
|
57
|
+
>;
|