react-native-snap-sheet 1.0.1 → 1.0.3
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/index.ts +9 -0
- package/package.json +3 -3
- package/src/snapsheet.js +147 -111
- package/src/snapsheet_modal.js +72 -35
- package/src/utils.js +3 -1
package/index.ts
CHANGED
|
@@ -104,6 +104,15 @@ export interface SnapSheetProps extends SnapSheetBaseProps {
|
|
|
104
104
|
*/
|
|
105
105
|
initialSnapIndex?: number;
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The lowest snap point the sheet can attain.
|
|
109
|
+
*
|
|
110
|
+
* This is useful in preventing the sheet from completing closing
|
|
111
|
+
*
|
|
112
|
+
* @default 0
|
|
113
|
+
*/
|
|
114
|
+
minSnapIndex?: number;
|
|
115
|
+
|
|
107
116
|
/**
|
|
108
117
|
* Disable user interactions on the snap sheet
|
|
109
118
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-snap-sheet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"homepage": "https://github.com/deflexable/react-native-snap-sheet#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"react-native-dodge-keyboard": "^1.0.
|
|
22
|
+
"react-native-dodge-keyboard": "^1.0.3",
|
|
23
23
|
"react-native-push-back": "^1.0.0"
|
|
24
24
|
}
|
|
25
|
-
}
|
|
25
|
+
}
|
package/src/snapsheet.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Animated,
|
|
3
|
-
import DodgeKeyboard, { ReactHijacker } from "react-native-dodge-keyboard";
|
|
4
|
-
import { doRendable, isNumber } from "./utils";
|
|
1
|
+
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, cloneElement } from "react";
|
|
2
|
+
import { Animated, PanResponder, StyleSheet, useAnimatedValue, View } from "react-native";
|
|
3
|
+
import DodgeKeyboard, { createHijackedElement, ReactHijacker, __HijackNode } from "react-native-dodge-keyboard";
|
|
4
|
+
import { doRendable, isNumber, isPositiveNumber } from "./utils";
|
|
5
5
|
import { styling } from "./styling";
|
|
6
6
|
|
|
7
7
|
const PixelRate = 70 / 100; // 70ms to 100 pixels
|
|
8
|
+
const CheckFocusedNode = '__fakeSnapSheetFocused';
|
|
8
9
|
|
|
9
10
|
const SnapSheet = forwardRef(function SnapSheet({
|
|
10
11
|
snapPoints = [],
|
|
11
12
|
initialSnapIndex = 0,
|
|
13
|
+
minSnapIndex = 0,
|
|
12
14
|
onSnapIndex,
|
|
13
15
|
onSnapFinish,
|
|
14
16
|
snapWhileDecelerating = false,
|
|
@@ -17,57 +19,79 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
17
19
|
inheritScrollVelocityOnCollapse,
|
|
18
20
|
renderHandle,
|
|
19
21
|
handleColor,
|
|
20
|
-
keyboardDodgingBehaviour = 'optimum',
|
|
22
|
+
keyboardDodgingBehaviour = 'optimum',
|
|
21
23
|
keyboardDodgingOffset = 10,
|
|
22
24
|
children,
|
|
23
25
|
disabled,
|
|
24
26
|
currentAnchorId,
|
|
27
|
+
__checkIfElementIsFocused,
|
|
28
|
+
__loosenMinSnap,
|
|
25
29
|
__shaky_sheet
|
|
26
30
|
}, ref) {
|
|
27
31
|
const isLift = keyboardDodgingBehaviour === 'whole';
|
|
28
|
-
const isOptimum = keyboardDodgingBehaviour === 'optimum';
|
|
29
32
|
|
|
30
33
|
if (!['optimum', 'whole', 'off'].includes(keyboardDodgingBehaviour))
|
|
31
34
|
throw `keyboardDodgingBehaviour must be any of ${['optimum', 'whole', 'off']} but got ${keyboardDodgingBehaviour}`;
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
if (snapPoints.length < 2) throw new Error('snapPoints must have at least two items');
|
|
37
|
+
snapPoints.forEach((v, i, a) => {
|
|
38
|
+
if (typeof v !== 'number' || !isNumber(v))
|
|
39
|
+
throw new Error(`snapPoints must have a valid number but got ${v} at position ${i}`);
|
|
40
|
+
if (i !== a.length - 1 && v >= a[i + 1])
|
|
41
|
+
throw new Error(`snapPoints must be in accending order but got ${v} before ${a[i + 1]}`);
|
|
42
|
+
});
|
|
43
|
+
if (!Number.isInteger(initialSnapIndex) || initialSnapIndex < 0)
|
|
44
|
+
throw new Error(`initialSnapIndex should be a positive integer but got:${initialSnapIndex}`);
|
|
45
|
+
if (initialSnapIndex >= snapPoints.length) throw new Error(`initialSnapIndex is out of range`);
|
|
46
|
+
|
|
47
|
+
if (!Number.isInteger(minSnapIndex) || minSnapIndex < 0)
|
|
48
|
+
throw new Error(`minSnapIndex should be a positive integer but got:${minSnapIndex}`);
|
|
49
|
+
|
|
50
|
+
if (minSnapIndex >= snapPoints.length) throw new Error(`minSnapIndex is out of range`);
|
|
51
|
+
initialSnapIndex = Math.max(initialSnapIndex, minSnapIndex);
|
|
52
|
+
|
|
53
|
+
if (__checkIfElementIsFocused !== undefined && typeof __checkIfElementIsFocused !== 'function')
|
|
54
|
+
throw `expected '__checkIfElementIsFocused' to be a function but got ${__checkIfElementIsFocused}`;
|
|
55
|
+
|
|
56
|
+
if (isLift) __checkIfElementIsFocused = r => !!r?.[CheckFocusedNode];
|
|
57
|
+
|
|
58
|
+
const flattenStyle = StyleSheet.flatten(style) || {};
|
|
46
59
|
|
|
47
60
|
const [scrollEnabled, setScrollEnabled] = useState(false);
|
|
48
61
|
const [dodgeOffset, setDodgeOffset] = useState(0);
|
|
49
|
-
const [requiredLift, setRequiredLift] = useState(0);
|
|
50
62
|
const [currentIndex, setCurrentIndex] = useState(initialSnapIndex);
|
|
51
63
|
const [finishedIndex, setFinishedIndex] = useState(initialSnapIndex);
|
|
52
64
|
const [prefferedAnchor, setPrefferedAnchor] = useState();
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
snapPoints = snapPoints.map(v => v + dodgeOffset);
|
|
67
|
+
const snapPointsKey = `${snapPoints}`;
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
const fixHeight =
|
|
70
|
+
(isPositiveNumber(flattenStyle.minHeight) && isPositiveNumber(flattenStyle.height))
|
|
71
|
+
? Math.max(flattenStyle.minHeight, flattenStyle.height)
|
|
72
|
+
: flattenStyle.height;
|
|
73
|
+
const fixMaxHeight = flattenStyle.maxHeight;
|
|
74
|
+
|
|
75
|
+
const PotentialHeight =
|
|
76
|
+
(isPositiveNumber(fixHeight) && isPositiveNumber(fixMaxHeight))
|
|
77
|
+
? Math.min(fixHeight, fixMaxHeight)
|
|
78
|
+
: fixHeight;
|
|
57
79
|
// console.log('sheetLifing:', { extraLift, dodgeOffset, requiredLift, initSnapPoints: `${initSnapPoints}`, snapPoints: `${snapPoints}` });
|
|
58
|
-
const
|
|
80
|
+
const MODAL_HEIGHT =
|
|
81
|
+
isPositiveNumber(PotentialHeight)
|
|
82
|
+
? PotentialHeight
|
|
83
|
+
: snapPoints.slice(-1)[0] - snapPoints[0];
|
|
59
84
|
|
|
60
|
-
const snapTranslateValues = useMemo(() => snapPoints.map(h =>
|
|
85
|
+
const snapTranslateValues = useMemo(() => snapPoints.map(h => h - MODAL_HEIGHT), [snapPointsKey]);
|
|
61
86
|
|
|
62
|
-
const
|
|
87
|
+
const bottomY = useAnimatedValue(snapTranslateValues[initialSnapIndex]);
|
|
63
88
|
|
|
64
89
|
/**
|
|
65
90
|
* @type {import("react").RefObject<{[key: string]: { ref: import('react-native').ScrollView, scrollY: 0, location: number[], anchorId: boolean }}>}
|
|
66
91
|
*/
|
|
67
92
|
const scrollRefObj = useRef({});
|
|
68
|
-
const lastOffset = useRef(
|
|
93
|
+
const lastOffset = useRef(bottomY._value);
|
|
69
94
|
const lastSnapIndex = useRef(initialSnapIndex);
|
|
70
|
-
const bottomFakePlaceholderRef = useRef();
|
|
71
95
|
const instantPrefferAnchor = useRef();
|
|
72
96
|
instantPrefferAnchor.current = prefferedAnchor;
|
|
73
97
|
|
|
@@ -77,42 +101,30 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
77
101
|
const instantScrollEnabled = useRef(scrollEnabled);
|
|
78
102
|
instantScrollEnabled.current = scrollEnabled;
|
|
79
103
|
|
|
80
|
-
const updateKeyboardOffset = () => {
|
|
81
|
-
if (!isLift) {
|
|
82
|
-
setRequiredLift(0);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const keyboardInfo = Keyboard.metrics();
|
|
86
|
-
if (keyboardInfo?.height && keyboardInfo.screenY) {
|
|
87
|
-
bottomFakePlaceholderRef.current.measureInWindow((x, y) => {
|
|
88
|
-
const remains = y - keyboardInfo.screenY;
|
|
89
|
-
setRequiredLift(Math.max(0, remains));
|
|
90
|
-
});
|
|
91
|
-
} else setRequiredLift(0);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
useEffect(updateKeyboardOffset, [dodgeOffset, ...initSnapPoints]);
|
|
95
|
-
|
|
96
104
|
const getCurrentSnap = (draggingUpward) => {
|
|
97
|
-
const shownHeight =
|
|
105
|
+
const shownHeight = bottomY._value + MODAL_HEIGHT;
|
|
98
106
|
const currentSnapIndex = draggingUpward ? snapPoints.findIndex((v, i, a) => v <= shownHeight && (i === a.length - 1 || shownHeight < a[i + 1]))
|
|
99
107
|
: snapPoints.findIndex((v, i, a) => v >= shownHeight && (!i || shownHeight > a[i - 1]));
|
|
100
108
|
|
|
101
109
|
return currentSnapIndex;
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
const snapToIndex = (
|
|
112
|
+
const snapToIndex = useRef();
|
|
113
|
+
|
|
114
|
+
snapToIndex.current = (index, force, velocity, onFinish) => {
|
|
105
115
|
if (disabled && !force) return;
|
|
106
116
|
|
|
107
117
|
if (!Number.isInteger(index) || index < 0 || index > snapPoints.length - 1)
|
|
108
118
|
throw new Error(`invalid snap index:${index}, index must be within range 0 - ${snapPoints.length - 1}`);
|
|
109
119
|
|
|
120
|
+
if (index < minSnapIndex) index = minSnapIndex;
|
|
121
|
+
|
|
110
122
|
const newY = snapTranslateValues[index];
|
|
111
123
|
|
|
112
124
|
if (__shaky_sheet && lastOffset.current !== newY)
|
|
113
|
-
|
|
125
|
+
bottomY.setValue(lastOffset.current);
|
|
114
126
|
|
|
115
|
-
const prevY =
|
|
127
|
+
const prevY = bottomY._value;
|
|
116
128
|
setScrollEnabled(index === snapPoints.length - 1);
|
|
117
129
|
setCurrentIndex(index);
|
|
118
130
|
|
|
@@ -131,7 +143,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
131
143
|
|
|
132
144
|
// console.log('snapTimer:', { timeout, pixel });
|
|
133
145
|
|
|
134
|
-
Animated.spring(
|
|
146
|
+
Animated.spring(bottomY, {
|
|
135
147
|
velocity,
|
|
136
148
|
toValue: newY,
|
|
137
149
|
useNativeDriver: true
|
|
@@ -148,13 +160,13 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
148
160
|
|
|
149
161
|
useImperativeHandle(ref, () => ({
|
|
150
162
|
snap: index => {
|
|
151
|
-
snapToIndex(index, true);
|
|
163
|
+
snapToIndex.current(index, true);
|
|
152
164
|
}
|
|
153
|
-
})
|
|
165
|
+
}));
|
|
154
166
|
|
|
155
167
|
useEffect(() => {
|
|
156
|
-
snapToIndex(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
|
|
157
|
-
},
|
|
168
|
+
snapToIndex.current(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
|
|
169
|
+
}, [snapPointsKey]);
|
|
158
170
|
|
|
159
171
|
const panResponder = useMemo(() => {
|
|
160
172
|
|
|
@@ -162,20 +174,28 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
162
174
|
onMoveShouldSetPanResponderCapture: (_, gesture) => {
|
|
163
175
|
const { scrollY } = scrollRefObj.current[instantPrefferAnchor.current] || {};
|
|
164
176
|
|
|
177
|
+
const isMovingY = (minChange = 3) =>
|
|
178
|
+
gesture.dy > minChange &&
|
|
179
|
+
(gesture.dy / (gesture.dy + gesture.dx)) >= .75;
|
|
180
|
+
|
|
165
181
|
const shouldCapture = !disabled && (
|
|
166
182
|
!instantScrollEnabled.current ||
|
|
167
|
-
(scrollY <= 0 &&
|
|
183
|
+
(scrollY <= 0 && isMovingY(5)) ||
|
|
184
|
+
(instantPrefferAnchor.current === undefined && isMovingY(10))
|
|
168
185
|
);
|
|
186
|
+
if (shouldCapture) setScrollEnabled(false);
|
|
169
187
|
// console.log('onMoveShouldSetPanResponderCapture shouldCapture:', shouldCapture, ' stats:', { gesture, scrollOffset: scrollY, instantScrollEnabled: instantScrollEnabled.current }, ' gesture.dy > 0:', gesture.dy > 1);
|
|
170
188
|
return shouldCapture;
|
|
171
189
|
},
|
|
172
190
|
onPanResponderMove: (_, gesture) => {
|
|
173
191
|
const newY = gesture.dy + lastOffset.current;
|
|
174
192
|
|
|
175
|
-
if (
|
|
176
|
-
|
|
193
|
+
if (
|
|
194
|
+
newY < snapTranslateValues[__loosenMinSnap ? 0 : minSnapIndex] ||
|
|
195
|
+
newY > snapTranslateValues.slice(-1)[0]
|
|
196
|
+
) return;
|
|
177
197
|
|
|
178
|
-
|
|
198
|
+
bottomY.setValue(newY);
|
|
179
199
|
},
|
|
180
200
|
onPanResponderRelease: (_, gesture) => {
|
|
181
201
|
const { dy, vy } = gesture; // when vy is lesser, it is scroll up
|
|
@@ -190,7 +210,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
190
210
|
vy > 0.3 ? 0 : currentSnapIndex;
|
|
191
211
|
const willFullyShow = newSnapIndex === snapPoints.length - 1;
|
|
192
212
|
|
|
193
|
-
snapToIndex(newSnapIndex, true, draggingUpward ? vy : undefined);
|
|
213
|
+
snapToIndex.current(newSnapIndex, true, draggingUpward ? vy : undefined);
|
|
194
214
|
|
|
195
215
|
// Only scroll if there was a fling velocity upward
|
|
196
216
|
if (inheritScrollVelocityOnExpand && willFullyShow && vy < -0.1) {
|
|
@@ -208,22 +228,24 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
208
228
|
}
|
|
209
229
|
}
|
|
210
230
|
});
|
|
211
|
-
}, [!disabled,
|
|
212
|
-
|
|
213
|
-
const conStyle = useMemo(() =>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
231
|
+
}, [!disabled, snapPointsKey, minSnapIndex]);
|
|
232
|
+
|
|
233
|
+
const conStyle = useMemo(() => {
|
|
234
|
+
const { height, minHeight, maxHeight, ...rest } = flattenStyle;
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
position: "absolute",
|
|
238
|
+
width: "100%",
|
|
239
|
+
backgroundColor: "#fff",
|
|
240
|
+
borderTopLeftRadius: 25,
|
|
241
|
+
borderTopRightRadius: 25,
|
|
242
|
+
zIndex: 1,
|
|
243
|
+
height: (Object.hasOwn(flattenStyle, 'height') && height === undefined) ? undefined : MODAL_HEIGHT,
|
|
244
|
+
...rest,
|
|
245
|
+
bottom: bottomY
|
|
246
|
+
};
|
|
247
|
+
}, [snapPointsKey, style]);
|
|
248
|
+
|
|
227
249
|
const updateAnchorReducer = useRef();
|
|
228
250
|
|
|
229
251
|
const scheduleAnchorUpdate = (timeout = 100) => {
|
|
@@ -234,12 +256,9 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
234
256
|
const updatePrefferAnchor = () => {
|
|
235
257
|
const rankedAnchors = Object.entries(scrollRefObj.current).sort((a, b) => compareReactPaths(a[1].location, b[1].location));
|
|
236
258
|
const directAnchor = rankedAnchors.find(v => v[1].anchorId === currentAnchorId);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const normalAnchor = rankedAnchors.find(v => !!v[1].anchorId);
|
|
240
|
-
if (normalAnchor) return setPrefferedAnchor(normalAnchor[0]);
|
|
241
|
-
setPrefferedAnchor(rankedAnchors[0]?.[0]);
|
|
259
|
+
setPrefferedAnchor(directAnchor?.[0]);
|
|
242
260
|
}
|
|
261
|
+
useEffect(updatePrefferAnchor, [currentAnchorId]);
|
|
243
262
|
|
|
244
263
|
const onAnchorScroll = (e, instanceId) => {
|
|
245
264
|
const scrollY = e.nativeEvent.contentOffset.y;
|
|
@@ -271,7 +290,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
271
290
|
|
|
272
291
|
const currentSnapIndex = getCurrentSnap(false);
|
|
273
292
|
const newSnapIndex = snapWhileDecelerating ? Math.max(0, currentSnapIndex - 1) : 0;
|
|
274
|
-
snapToIndex(newSnapIndex, false, -scrollVelocity);
|
|
293
|
+
snapToIndex.current(newSnapIndex, false, -scrollVelocity);
|
|
275
294
|
}
|
|
276
295
|
}
|
|
277
296
|
|
|
@@ -283,6 +302,8 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
283
302
|
const disableDodging = keyboardDodgingBehaviour === 'off';
|
|
284
303
|
const sameIndex = currentIndex === finishedIndex;
|
|
285
304
|
|
|
305
|
+
const instanceIdIterator = useRef(0);
|
|
306
|
+
|
|
286
307
|
return (
|
|
287
308
|
<View style={styling.absoluteFill}>
|
|
288
309
|
<Animated.View
|
|
@@ -298,16 +319,19 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
298
319
|
<DodgeKeyboard
|
|
299
320
|
offset={keyboardDodgingOffset}
|
|
300
321
|
disabled={!sameIndex || disableDodging}
|
|
322
|
+
checkIfElementIsFocused={__checkIfElementIsFocused}
|
|
301
323
|
onHandleDodging={({ liftUp }) => {
|
|
302
324
|
setDodgeOffset(liftUp);
|
|
303
325
|
}}>
|
|
304
326
|
{ReactHijacker({
|
|
305
327
|
children,
|
|
328
|
+
enableLocator: true,
|
|
306
329
|
doHijack: (node, path) => {
|
|
307
|
-
if (node?.props?.['snap_sheet_scan_off']
|
|
330
|
+
if (node?.props?.['snap_sheet_scan_off'] || node?.props?.__checking_snap_scrollable) return;
|
|
331
|
+
if (!isScrollable(node)) return;
|
|
308
332
|
|
|
309
|
-
|
|
310
|
-
const instanceId =
|
|
333
|
+
const renderer = () => {
|
|
334
|
+
const instanceId = useMemo(() => `${++instanceIdIterator.current}`, []);
|
|
311
335
|
|
|
312
336
|
const initNode = () => {
|
|
313
337
|
if (!scrollRefObj.current[instanceId])
|
|
@@ -321,43 +345,55 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
321
345
|
}
|
|
322
346
|
initNode();
|
|
323
347
|
|
|
324
|
-
|
|
325
|
-
props
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const thatRef = node.props?.ref;
|
|
339
|
-
if (typeof thatRef === 'function') {
|
|
340
|
-
thatRef(r);
|
|
341
|
-
} else if (thatRef) thatRef.current = r;
|
|
342
|
-
},
|
|
343
|
-
...prefferedAnchor === instanceId ? { scrollEnabled } : {},
|
|
344
|
-
onScroll: (e) => {
|
|
345
|
-
onAnchorScroll(e, instanceId);
|
|
346
|
-
return node.props?.onScroll?.(e);
|
|
348
|
+
const newProps = {
|
|
349
|
+
...node?.props,
|
|
350
|
+
__checking_snap_scrollable: true,
|
|
351
|
+
...disableDodging ? {} : { ['dodge_keyboard_scrollable']: true },
|
|
352
|
+
ref: r => {
|
|
353
|
+
if (r) {
|
|
354
|
+
initNode();
|
|
355
|
+
// if (scrollRefObj.current[instanceId].ref !== r) scheduleAnchorUpdate();
|
|
356
|
+
scrollRefObj.current[instanceId].ref = r;
|
|
357
|
+
} else if (scrollRefObj.current[instanceId]) {
|
|
358
|
+
delete scrollRefObj.current[instanceId];
|
|
359
|
+
scheduleAnchorUpdate();
|
|
347
360
|
}
|
|
361
|
+
|
|
362
|
+
const thatRef = node.props?.ref;
|
|
363
|
+
if (typeof thatRef === 'function') {
|
|
364
|
+
thatRef(r);
|
|
365
|
+
} else if (thatRef) thatRef.current = r;
|
|
366
|
+
},
|
|
367
|
+
...prefferedAnchor === instanceId ? { scrollEnabled } : {},
|
|
368
|
+
onScroll: (e) => {
|
|
369
|
+
onAnchorScroll(e, instanceId);
|
|
370
|
+
return node.props?.onScroll?.(e);
|
|
348
371
|
}
|
|
349
|
-
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return cloneElement(node, newProps);
|
|
350
375
|
}
|
|
376
|
+
|
|
377
|
+
return createHijackedElement(
|
|
378
|
+
<__HijackNode>
|
|
379
|
+
{renderer}
|
|
380
|
+
</__HijackNode>
|
|
381
|
+
);
|
|
351
382
|
}
|
|
352
383
|
})}
|
|
384
|
+
{isLift ?
|
|
385
|
+
<View
|
|
386
|
+
ref={r => {
|
|
387
|
+
if (r) {
|
|
388
|
+
r[CheckFocusedNode] = true;
|
|
389
|
+
}
|
|
390
|
+
}}
|
|
391
|
+
dodge_keyboard_input
|
|
392
|
+
style={styling.fakePlaceholder}
|
|
393
|
+
/> : null}
|
|
353
394
|
</DodgeKeyboard>
|
|
354
395
|
</View>
|
|
355
396
|
</Animated.View>
|
|
356
|
-
{isLift ?
|
|
357
|
-
<View
|
|
358
|
-
ref={bottomFakePlaceholderRef}
|
|
359
|
-
style={styling.fakePlaceholder}
|
|
360
|
-
onLayout={updateKeyboardOffset} /> : null}
|
|
361
397
|
</View>
|
|
362
398
|
);
|
|
363
399
|
});
|
package/src/snapsheet_modal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState } from "react";
|
|
2
|
-
import { Pressable, StyleSheet, View } from "react-native";
|
|
3
|
-
import { isDodgeInput, ReactHijacker } from "react-native-dodge-keyboard";
|
|
1
|
+
import { forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState, cloneElement } from "react";
|
|
2
|
+
import { Pressable, StyleSheet, View, PixelRatio } from "react-native";
|
|
3
|
+
import { createHijackedElement, isDodgeInput, ReactHijacker, __HijackNode } from "react-native-dodge-keyboard";
|
|
4
4
|
import { useBackButton } from "react-native-push-back";
|
|
5
5
|
import { doRendable, isNumber } from "./utils";
|
|
6
6
|
import { PortalContext } from "./provider";
|
|
@@ -8,7 +8,8 @@ import { styling } from "./styling";
|
|
|
8
8
|
import SnapSheet from "./snapsheet";
|
|
9
9
|
|
|
10
10
|
const ModalState = ['closed', 'middle', 'opened'];
|
|
11
|
-
const CenteredSheetStyle = { width: 0 };
|
|
11
|
+
const CenteredSheetStyle = { width: 0, height: undefined };
|
|
12
|
+
const CheckFocusedNode = '__fakeSnapSheetModalFocused';
|
|
12
13
|
|
|
13
14
|
export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
14
15
|
onOpened,
|
|
@@ -29,6 +30,8 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
29
30
|
children,
|
|
30
31
|
...restProps
|
|
31
32
|
}, ref) {
|
|
33
|
+
const isLift = restProps?.keyboardDodgingBehaviour === 'whole';
|
|
34
|
+
|
|
32
35
|
centered = !!centered;
|
|
33
36
|
useMemo(() => {
|
|
34
37
|
if (centered) {
|
|
@@ -90,7 +93,7 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
90
93
|
const snapPoints = useMemo(() => {
|
|
91
94
|
if (centered) {
|
|
92
95
|
if (sizingReady)
|
|
93
|
-
return [
|
|
96
|
+
return [0, (viewHeight / 2) + (contentHeight / 2)];
|
|
94
97
|
return [0, .3];
|
|
95
98
|
} else return [0, ...isNumber(middleHeight) ? [middleHeight] : [], modalHeight];
|
|
96
99
|
}, [viewHeight, contentHeight, centered, middleHeight, modalHeight]);
|
|
@@ -173,12 +176,13 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
173
176
|
|
|
174
177
|
const centeredStyle = useMemo(() => centered ? ({
|
|
175
178
|
position: 'absolute',
|
|
176
|
-
width: viewWidth
|
|
179
|
+
width: viewWidth,
|
|
177
180
|
left: 0,
|
|
178
|
-
top: 0
|
|
179
|
-
marginTop: -(contentHeight / 2) || 0
|
|
181
|
+
top: 0
|
|
180
182
|
}) : undefined, [centered, contentHeight, viewWidth]);
|
|
181
183
|
|
|
184
|
+
const inputIdIterator = useRef(0);
|
|
185
|
+
|
|
182
186
|
const renderChild = () =>
|
|
183
187
|
<View
|
|
184
188
|
style={styling.absoluteFill}
|
|
@@ -197,39 +201,52 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
197
201
|
}} />
|
|
198
202
|
)}
|
|
199
203
|
<ReactHijacker
|
|
200
|
-
doHijack={
|
|
201
|
-
if (isDodgeInput(node))
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const thatRef = node.props?.ref;
|
|
215
|
-
if (typeof thatRef === 'function') {
|
|
216
|
-
thatRef(r);
|
|
217
|
-
} else if (thatRef) thatRef.current = r;
|
|
204
|
+
doHijack={node => {
|
|
205
|
+
if (!isDodgeInput(node)) return;
|
|
206
|
+
|
|
207
|
+
const renderer = () => {
|
|
208
|
+
const inputId = useMemo(() => `${++inputIdIterator.current}`, []);
|
|
209
|
+
|
|
210
|
+
const newProps = {
|
|
211
|
+
...node.props,
|
|
212
|
+
ref: r => {
|
|
213
|
+
if (r) {
|
|
214
|
+
inputRefs.current[inputId] = r;
|
|
215
|
+
} else if (inputRefs.current[inputId]) {
|
|
216
|
+
delete inputRefs.current[inputId];
|
|
218
217
|
}
|
|
218
|
+
|
|
219
|
+
const thatRef = node.props?.ref;
|
|
220
|
+
if (typeof thatRef === 'function') {
|
|
221
|
+
thatRef(r);
|
|
222
|
+
} else if (thatRef) thatRef.current = r;
|
|
219
223
|
}
|
|
220
224
|
};
|
|
225
|
+
|
|
226
|
+
return cloneElement(node, newProps);
|
|
221
227
|
}
|
|
228
|
+
|
|
229
|
+
return createHijackedElement(
|
|
230
|
+
<__HijackNode>
|
|
231
|
+
{renderer}
|
|
232
|
+
</__HijackNode>
|
|
233
|
+
);
|
|
222
234
|
}}>
|
|
223
235
|
<SnapSheet
|
|
224
236
|
{...restProps}
|
|
237
|
+
minSnapIndex={0}
|
|
225
238
|
ref={sheetRef}
|
|
226
239
|
snapPoints={snapPoints}
|
|
227
|
-
{...hasClosed ? { keyboardDodgingBehaviour: 'off' } : {}}
|
|
228
240
|
{...centered ? {
|
|
229
241
|
style: CenteredSheetStyle,
|
|
230
|
-
renderHandle: null
|
|
242
|
+
renderHandle: null,
|
|
243
|
+
__shaky_sheet: true,
|
|
244
|
+
...isLift ? {
|
|
245
|
+
__checkIfElementIsFocused: r => !!r?.[CheckFocusedNode],
|
|
246
|
+
keyboardDodgingBehaviour: 'optimum'
|
|
247
|
+
} : {}
|
|
231
248
|
} : {}}
|
|
232
|
-
|
|
249
|
+
{...hasClosed ? { keyboardDodgingBehaviour: 'off' } : {}}
|
|
233
250
|
initialSnapIndex={Math.min(ModalState.indexOf(currentState), centered ? 1 : 2)}
|
|
234
251
|
disabled={centered || disabled || disablePanGesture}
|
|
235
252
|
onSnapFinish={i => {
|
|
@@ -240,14 +257,34 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
240
257
|
}}>
|
|
241
258
|
{(hasClosed && (!releaseUnmount && unMountChildrenWhenClosed))
|
|
242
259
|
? null :
|
|
243
|
-
<View
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
<View
|
|
261
|
+
style={centered ? centeredStyle : styling.flexer}
|
|
262
|
+
onLayout={e => {
|
|
263
|
+
if (centered) {
|
|
264
|
+
const h = e.nativeEvent.layout.height;
|
|
265
|
+
|
|
266
|
+
if (contentHeight === undefined)
|
|
267
|
+
return setContentHeight(PixelRatio.roundToNearestPixel(h));
|
|
268
|
+
|
|
269
|
+
const pixel = PixelRatio.roundToNearestPixel(h);
|
|
270
|
+
|
|
271
|
+
if (Math.abs(contentHeight - pixel) >= 1)
|
|
272
|
+
setContentHeight(pixel);
|
|
273
|
+
}
|
|
274
|
+
}}>
|
|
275
|
+
<View style={centered ? restProps.style : styling.flexer}>
|
|
249
276
|
{children}
|
|
250
277
|
</View>
|
|
278
|
+
{isLift ?
|
|
279
|
+
<View
|
|
280
|
+
ref={r => {
|
|
281
|
+
if (r) {
|
|
282
|
+
r[CheckFocusedNode] = true;
|
|
283
|
+
}
|
|
284
|
+
}}
|
|
285
|
+
dodge_keyboard_input
|
|
286
|
+
style={styling.fakePlaceholder}
|
|
287
|
+
/> : null}
|
|
251
288
|
</View>}
|
|
252
289
|
</SnapSheet>
|
|
253
290
|
</ReactHijacker>
|
package/src/utils.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
export const doRendable = (e, d) => e === undefined ? d : typeof e === 'function' ? e() : e;
|
|
3
3
|
|
|
4
|
-
export const isNumber = t => typeof t === 'number' && !isNaN(t) && Number.isFinite(t);
|
|
4
|
+
export const isNumber = t => typeof t === 'number' && !isNaN(t) && Number.isFinite(t);
|
|
5
|
+
|
|
6
|
+
export const isPositiveNumber = t => isNumber(t) && t >= 0;
|