react-native-snap-sheet 1.0.2 → 1.0.4
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 +10 -1
- package/package.json +3 -3
- package/src/snapsheet.js +186 -119
- package/src/snapsheet_modal.js +92 -40
- package/src/utils.js +3 -1
package/index.ts
CHANGED
|
@@ -86,7 +86,7 @@ export interface SnapSheetBaseProps {
|
|
|
86
86
|
/**
|
|
87
87
|
* Additional offset to add when dodging keyboard
|
|
88
88
|
*
|
|
89
|
-
*
|
|
89
|
+
* default to `0` if `keyboardDodgingBehaviour` is "whole" otherwise, it defaults to `10`
|
|
90
90
|
*/
|
|
91
91
|
keyboardDodgingOffset?: number;
|
|
92
92
|
}
|
|
@@ -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.4",
|
|
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.4",
|
|
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,47 +19,80 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
17
19
|
inheritScrollVelocityOnCollapse,
|
|
18
20
|
renderHandle,
|
|
19
21
|
handleColor,
|
|
20
|
-
keyboardDodgingBehaviour = 'optimum',
|
|
21
|
-
keyboardDodgingOffset
|
|
22
|
+
keyboardDodgingBehaviour = 'optimum',
|
|
23
|
+
keyboardDodgingOffset,
|
|
22
24
|
children,
|
|
23
25
|
disabled,
|
|
24
26
|
currentAnchorId,
|
|
25
|
-
|
|
27
|
+
__checkIfElementIsFocused,
|
|
28
|
+
__loosenMinSnap
|
|
26
29
|
}, ref) {
|
|
27
30
|
const isLift = keyboardDodgingBehaviour === 'whole';
|
|
28
|
-
const isOptimum = keyboardDodgingBehaviour === 'optimum';
|
|
29
31
|
|
|
30
32
|
if (!['optimum', 'whole', 'off'].includes(keyboardDodgingBehaviour))
|
|
31
33
|
throw `keyboardDodgingBehaviour must be any of ${['optimum', 'whole', 'off']} but got ${keyboardDodgingBehaviour}`;
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
if (snapPoints.length < 2) throw new Error('snapPoints must have at least two items');
|
|
36
|
+
snapPoints.forEach((v, i, a) => {
|
|
37
|
+
if (typeof v !== 'number' || !isNumber(v))
|
|
38
|
+
throw new Error(`snapPoints must have a valid number but got ${v} at position ${i}`);
|
|
39
|
+
if (i !== a.length - 1 && v >= a[i + 1])
|
|
40
|
+
throw new Error(`snapPoints must be in accending order but got ${v} before ${a[i + 1]}`);
|
|
41
|
+
});
|
|
42
|
+
if (!Number.isInteger(initialSnapIndex) || initialSnapIndex < 0)
|
|
43
|
+
throw new Error(`initialSnapIndex should be a positive integer but got:${initialSnapIndex}`);
|
|
44
|
+
if (initialSnapIndex >= snapPoints.length) throw new Error(`initialSnapIndex is out of range`);
|
|
45
|
+
|
|
46
|
+
if (!Number.isInteger(minSnapIndex) || minSnapIndex < 0)
|
|
47
|
+
throw new Error(`minSnapIndex should be a positive integer but got:${minSnapIndex}`);
|
|
48
|
+
|
|
49
|
+
if (minSnapIndex >= snapPoints.length) throw new Error(`minSnapIndex is out of range`);
|
|
50
|
+
initialSnapIndex = Math.max(initialSnapIndex, minSnapIndex);
|
|
51
|
+
|
|
52
|
+
if (__checkIfElementIsFocused !== undefined && typeof __checkIfElementIsFocused !== 'function')
|
|
53
|
+
throw `expected '__checkIfElementIsFocused' to be a function but got ${__checkIfElementIsFocused}`;
|
|
54
|
+
|
|
55
|
+
if (isLift) {
|
|
56
|
+
const realChecker = __checkIfElementIsFocused;
|
|
57
|
+
__checkIfElementIsFocused = (r, refs) => {
|
|
58
|
+
return !!r?.[CheckFocusedNode] && refs.some(v => realChecker ? realChecker?.(v) : v?.isFocused?.());
|
|
59
|
+
};
|
|
60
|
+
if (keyboardDodgingOffset === undefined) {
|
|
61
|
+
keyboardDodgingOffset = 0;
|
|
62
|
+
}
|
|
63
|
+
} else if (keyboardDodgingOffset === undefined) {
|
|
64
|
+
keyboardDodgingOffset = 10;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const flattenStyle = StyleSheet.flatten(style) || {};
|
|
45
68
|
const initSnapPoints = snapPoints;
|
|
46
69
|
|
|
47
70
|
const [scrollEnabled, setScrollEnabled] = useState(false);
|
|
48
71
|
const [dodgeOffset, setDodgeOffset] = useState(0);
|
|
49
|
-
const [requiredLift, setRequiredLift] = useState(0);
|
|
50
72
|
const [currentIndex, setCurrentIndex] = useState(initialSnapIndex);
|
|
51
73
|
const [finishedIndex, setFinishedIndex] = useState(initialSnapIndex);
|
|
52
74
|
const [prefferedAnchor, setPrefferedAnchor] = useState();
|
|
53
75
|
|
|
54
|
-
|
|
76
|
+
snapPoints = snapPoints.map(v => v + dodgeOffset);
|
|
77
|
+
const snapPointsKey = `${snapPoints}`;
|
|
55
78
|
|
|
56
|
-
|
|
79
|
+
const fixHeight =
|
|
80
|
+
(isPositiveNumber(flattenStyle.minHeight) && isPositiveNumber(flattenStyle.height))
|
|
81
|
+
? Math.max(flattenStyle.minHeight, flattenStyle.height)
|
|
82
|
+
: flattenStyle.height;
|
|
83
|
+
const fixMaxHeight = flattenStyle.maxHeight;
|
|
84
|
+
|
|
85
|
+
const PotentialHeight =
|
|
86
|
+
(isPositiveNumber(fixHeight) && isPositiveNumber(fixMaxHeight))
|
|
87
|
+
? Math.min(fixHeight, fixMaxHeight)
|
|
88
|
+
: fixHeight;
|
|
57
89
|
// console.log('sheetLifing:', { extraLift, dodgeOffset, requiredLift, initSnapPoints: `${initSnapPoints}`, snapPoints: `${snapPoints}` });
|
|
58
|
-
const
|
|
90
|
+
const MODAL_HEIGHT =
|
|
91
|
+
isPositiveNumber(PotentialHeight)
|
|
92
|
+
? PotentialHeight
|
|
93
|
+
: snapPoints.slice(-1)[0] - snapPoints[0];
|
|
59
94
|
|
|
60
|
-
const snapTranslateValues = useMemo(() => snapPoints.map(h =>
|
|
95
|
+
const snapTranslateValues = useMemo(() => snapPoints.map(h => MODAL_HEIGHT - h), [snapPointsKey]);
|
|
61
96
|
|
|
62
97
|
const translateY = useAnimatedValue(snapTranslateValues[initialSnapIndex]);
|
|
63
98
|
|
|
@@ -67,7 +102,6 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
67
102
|
const scrollRefObj = useRef({});
|
|
68
103
|
const lastOffset = useRef(translateY._value);
|
|
69
104
|
const lastSnapIndex = useRef(initialSnapIndex);
|
|
70
|
-
const bottomFakePlaceholderRef = useRef();
|
|
71
105
|
const instantPrefferAnchor = useRef();
|
|
72
106
|
instantPrefferAnchor.current = prefferedAnchor;
|
|
73
107
|
|
|
@@ -77,40 +111,25 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
77
111
|
const instantScrollEnabled = useRef(scrollEnabled);
|
|
78
112
|
instantScrollEnabled.current = scrollEnabled;
|
|
79
113
|
|
|
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
114
|
const getCurrentSnap = (draggingUpward) => {
|
|
97
|
-
const shownHeight =
|
|
115
|
+
const shownHeight = MODAL_HEIGHT - translateY._value;
|
|
98
116
|
const currentSnapIndex = draggingUpward ? snapPoints.findIndex((v, i, a) => v <= shownHeight && (i === a.length - 1 || shownHeight < a[i + 1]))
|
|
99
117
|
: snapPoints.findIndex((v, i, a) => v >= shownHeight && (!i || shownHeight > a[i - 1]));
|
|
100
118
|
|
|
101
119
|
return currentSnapIndex;
|
|
102
120
|
}
|
|
103
121
|
|
|
104
|
-
const snapToIndex = (
|
|
122
|
+
const snapToIndex = useRef();
|
|
123
|
+
|
|
124
|
+
snapToIndex.current = (index, force, velocity, onFinish) => {
|
|
105
125
|
if (disabled && !force) return;
|
|
106
126
|
|
|
107
127
|
if (!Number.isInteger(index) || index < 0 || index > snapPoints.length - 1)
|
|
108
128
|
throw new Error(`invalid snap index:${index}, index must be within range 0 - ${snapPoints.length - 1}`);
|
|
109
129
|
|
|
110
|
-
|
|
130
|
+
if (index < minSnapIndex) index = minSnapIndex;
|
|
111
131
|
|
|
112
|
-
|
|
113
|
-
translateY.setValue(lastOffset.current);
|
|
132
|
+
const newY = snapTranslateValues[index];
|
|
114
133
|
|
|
115
134
|
const prevY = translateY._value;
|
|
116
135
|
setScrollEnabled(index === snapPoints.length - 1);
|
|
@@ -129,7 +148,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
129
148
|
|
|
130
149
|
const timer = setTimeout(guessFinish, Math.max(300, timeout));
|
|
131
150
|
|
|
132
|
-
// console.log('snapTimer:', { timeout, pixel });
|
|
151
|
+
// console.log('snapTimer:', { timeout, pixel }, ' newY:', newY, ' snapPoint:', initSnapPoints, ' snapTrans:', snapTranslateValues);
|
|
133
152
|
|
|
134
153
|
Animated.spring(translateY, {
|
|
135
154
|
velocity,
|
|
@@ -148,13 +167,13 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
148
167
|
|
|
149
168
|
useImperativeHandle(ref, () => ({
|
|
150
169
|
snap: index => {
|
|
151
|
-
snapToIndex(index, true);
|
|
170
|
+
snapToIndex.current(index, true);
|
|
152
171
|
}
|
|
153
|
-
})
|
|
172
|
+
}));
|
|
154
173
|
|
|
155
174
|
useEffect(() => {
|
|
156
|
-
snapToIndex(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
|
|
157
|
-
},
|
|
175
|
+
snapToIndex.current(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
|
|
176
|
+
}, [snapPointsKey]);
|
|
158
177
|
|
|
159
178
|
const panResponder = useMemo(() => {
|
|
160
179
|
|
|
@@ -178,8 +197,10 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
178
197
|
onPanResponderMove: (_, gesture) => {
|
|
179
198
|
const newY = gesture.dy + lastOffset.current;
|
|
180
199
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
200
|
+
if (
|
|
201
|
+
newY > snapTranslateValues[__loosenMinSnap ? 0 : minSnapIndex] ||
|
|
202
|
+
newY < snapTranslateValues.slice(-1)[0]
|
|
203
|
+
) return;
|
|
183
204
|
|
|
184
205
|
translateY.setValue(newY);
|
|
185
206
|
},
|
|
@@ -196,7 +217,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
196
217
|
vy > 0.3 ? 0 : currentSnapIndex;
|
|
197
218
|
const willFullyShow = newSnapIndex === snapPoints.length - 1;
|
|
198
219
|
|
|
199
|
-
snapToIndex(newSnapIndex, true, draggingUpward ? vy : undefined);
|
|
220
|
+
snapToIndex.current(newSnapIndex, true, draggingUpward ? vy : undefined);
|
|
200
221
|
|
|
201
222
|
// Only scroll if there was a fling velocity upward
|
|
202
223
|
if (inheritScrollVelocityOnExpand && willFullyShow && vy < -0.1) {
|
|
@@ -214,22 +235,22 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
214
235
|
}
|
|
215
236
|
}
|
|
216
237
|
});
|
|
217
|
-
}, [!disabled,
|
|
218
|
-
|
|
219
|
-
const conStyle = useMemo(() =>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
238
|
+
}, [!disabled, snapPointsKey, minSnapIndex]);
|
|
239
|
+
|
|
240
|
+
const conStyle = useMemo(() => {
|
|
241
|
+
const { height, minHeight, maxHeight, ...rest } = flattenStyle;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
backgroundColor: "#fff",
|
|
245
|
+
borderTopLeftRadius: 25,
|
|
246
|
+
borderTopRightRadius: 25,
|
|
247
|
+
zIndex: 1,
|
|
248
|
+
height: (Object.hasOwn(flattenStyle, 'height') && height === undefined) ? undefined : MODAL_HEIGHT,
|
|
249
|
+
...rest,
|
|
250
|
+
transform: [{ translateY }]
|
|
251
|
+
};
|
|
252
|
+
}, [snapPointsKey, style]);
|
|
253
|
+
|
|
233
254
|
const updateAnchorReducer = useRef();
|
|
234
255
|
|
|
235
256
|
const scheduleAnchorUpdate = (timeout = 100) => {
|
|
@@ -242,6 +263,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
242
263
|
const directAnchor = rankedAnchors.find(v => v[1].anchorId === currentAnchorId);
|
|
243
264
|
setPrefferedAnchor(directAnchor?.[0]);
|
|
244
265
|
}
|
|
266
|
+
useEffect(updatePrefferAnchor, [currentAnchorId]);
|
|
245
267
|
|
|
246
268
|
const onAnchorScroll = (e, instanceId) => {
|
|
247
269
|
const scrollY = e.nativeEvent.contentOffset.y;
|
|
@@ -273,7 +295,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
273
295
|
|
|
274
296
|
const currentSnapIndex = getCurrentSnap(false);
|
|
275
297
|
const newSnapIndex = snapWhileDecelerating ? Math.max(0, currentSnapIndex - 1) : 0;
|
|
276
|
-
snapToIndex(newSnapIndex, false, -scrollVelocity);
|
|
298
|
+
snapToIndex.current(newSnapIndex, false, -scrollVelocity);
|
|
277
299
|
}
|
|
278
300
|
}
|
|
279
301
|
|
|
@@ -285,47 +307,79 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
285
307
|
const disableDodging = keyboardDodgingBehaviour === 'off';
|
|
286
308
|
const sameIndex = currentIndex === finishedIndex;
|
|
287
309
|
|
|
310
|
+
const instanceIdIterator = useRef(0);
|
|
311
|
+
const prevKE = useRef();
|
|
312
|
+
|
|
313
|
+
const quicklyDodgeKeyboard = (offset, keyboardEvent) => {
|
|
314
|
+
// console.log('quicklyDodgeKeyboard offset:', offset, ' keyboardEvent:', keyboardEvent);
|
|
315
|
+
if (!keyboardEvent) {
|
|
316
|
+
if (!(keyboardEvent = prevKE.current)) return;
|
|
317
|
+
}
|
|
318
|
+
if (keyboardEvent.endCoordinates.height) {
|
|
319
|
+
prevKE.current = keyboardEvent;
|
|
320
|
+
} else {
|
|
321
|
+
if (prevKE.current) keyboardEvent = prevKE.current;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const newPosY = MODAL_HEIGHT - (initSnapPoints[lastSnapIndex.current] + offset);
|
|
325
|
+
const newDuration = (Math.abs(newPosY - translateY._value) * keyboardEvent.duration) / keyboardEvent?.endCoordinates.height;
|
|
326
|
+
|
|
327
|
+
// console.log('newPosY:', newPosY, ' timing newDuration:', newDuration);
|
|
328
|
+
Animated.timing(translateY, {
|
|
329
|
+
duration: newDuration || 0,
|
|
330
|
+
toValue: newPosY,
|
|
331
|
+
useNativeDriver: true
|
|
332
|
+
}).start();
|
|
333
|
+
}
|
|
334
|
+
|
|
288
335
|
return (
|
|
289
336
|
<View style={styling.absoluteFill}>
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
<View style={
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
<
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
337
|
+
<View style={{ position: 'absolute', bottom: 0, width: '100%' }}>
|
|
338
|
+
<Animated.View
|
|
339
|
+
style={conStyle}
|
|
340
|
+
{...panResponder.panHandlers}>
|
|
341
|
+
{doRendable?.(
|
|
342
|
+
renderHandle,
|
|
343
|
+
<View style={styling.modalHandle}>
|
|
344
|
+
<View style={handleDotStyle} />
|
|
345
|
+
</View>
|
|
346
|
+
)}
|
|
347
|
+
<View style={styling.flexer}>
|
|
348
|
+
<DodgeKeyboard
|
|
349
|
+
offset={keyboardDodgingOffset}
|
|
350
|
+
disabled={!sameIndex || disableDodging}
|
|
351
|
+
checkIfElementIsFocused={__checkIfElementIsFocused}
|
|
352
|
+
onHandleDodging={({ liftUp, keyboardEvent }) => {
|
|
353
|
+
quicklyDodgeKeyboard(liftUp, keyboardEvent);
|
|
354
|
+
setDodgeOffset(liftUp);
|
|
355
|
+
}}>
|
|
356
|
+
{ReactHijacker({
|
|
357
|
+
children,
|
|
358
|
+
enableLocator: true,
|
|
359
|
+
doHijack: (node, path) => {
|
|
360
|
+
if (node?.props?.snap_sheet_scan_off || node?.props?.__checking_snap_scrollable)
|
|
361
|
+
return createHijackedElement(node);
|
|
362
|
+
|
|
363
|
+
if (!isScrollable(node)) return;
|
|
364
|
+
|
|
365
|
+
const renderer = () => {
|
|
366
|
+
const instanceId = useMemo(() => `${++instanceIdIterator.current}`, []);
|
|
367
|
+
|
|
368
|
+
const initNode = () => {
|
|
369
|
+
if (!scrollRefObj.current[instanceId])
|
|
370
|
+
scrollRefObj.current[instanceId] = { scrollY: 0, location: path };
|
|
371
|
+
const thisAnchorId = node.props?.snap_sheet_scroll_anchor;
|
|
372
|
+
|
|
373
|
+
if (scrollRefObj.current[instanceId].anchorId !== thisAnchorId) {
|
|
374
|
+
scheduleAnchorUpdate(300);
|
|
375
|
+
}
|
|
376
|
+
scrollRefObj.current[instanceId].anchorId = thisAnchorId;
|
|
321
377
|
}
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
initNode();
|
|
378
|
+
initNode();
|
|
325
379
|
|
|
326
|
-
|
|
327
|
-
props: {
|
|
380
|
+
const newProps = {
|
|
328
381
|
...node?.props,
|
|
382
|
+
__checking_snap_scrollable: true,
|
|
329
383
|
...disableDodging ? {} : { ['dodge_keyboard_scrollable']: true },
|
|
330
384
|
ref: r => {
|
|
331
385
|
if (r) {
|
|
@@ -347,19 +401,32 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
347
401
|
onAnchorScroll(e, instanceId);
|
|
348
402
|
return node.props?.onScroll?.(e);
|
|
349
403
|
}
|
|
350
|
-
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
return cloneElement(node, newProps);
|
|
351
407
|
}
|
|
408
|
+
|
|
409
|
+
return createHijackedElement(
|
|
410
|
+
<__HijackNode>
|
|
411
|
+
{renderer}
|
|
412
|
+
</__HijackNode>
|
|
413
|
+
);
|
|
352
414
|
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
415
|
+
})}
|
|
416
|
+
{isLift ?
|
|
417
|
+
<View
|
|
418
|
+
ref={r => {
|
|
419
|
+
if (r) {
|
|
420
|
+
r[CheckFocusedNode] = true;
|
|
421
|
+
}
|
|
422
|
+
}}
|
|
423
|
+
dodge_keyboard_input
|
|
424
|
+
style={styling.fakePlaceholder}
|
|
425
|
+
/> : null}
|
|
426
|
+
</DodgeKeyboard>
|
|
427
|
+
</View>
|
|
428
|
+
</Animated.View>
|
|
429
|
+
</View>
|
|
363
430
|
</View>
|
|
364
431
|
);
|
|
365
432
|
});
|
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: 0 };
|
|
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) {
|
|
@@ -37,6 +40,9 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
37
40
|
}
|
|
38
41
|
}, [centered]);
|
|
39
42
|
|
|
43
|
+
if (restProps.__checkIfElementIsFocused !== undefined && typeof restProps.__checkIfElementIsFocused !== 'function')
|
|
44
|
+
throw `expected '__checkIfElementIsFocused' to be a function but got ${restProps.__checkIfElementIsFocused}`;
|
|
45
|
+
|
|
40
46
|
if (centered) {
|
|
41
47
|
middleHeight = undefined;
|
|
42
48
|
if (initialState === 'middle') initialState = 'opened';
|
|
@@ -90,7 +96,7 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
90
96
|
const snapPoints = useMemo(() => {
|
|
91
97
|
if (centered) {
|
|
92
98
|
if (sizingReady)
|
|
93
|
-
return [
|
|
99
|
+
return [0, (viewHeight / 2) + (contentHeight / 2)];
|
|
94
100
|
return [0, .3];
|
|
95
101
|
} else return [0, ...isNumber(middleHeight) ? [middleHeight] : [], modalHeight];
|
|
96
102
|
}, [viewHeight, contentHeight, centered, middleHeight, modalHeight]);
|
|
@@ -102,15 +108,23 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
102
108
|
const sheetRef = useRef();
|
|
103
109
|
const inputRefs = useRef({});
|
|
104
110
|
const snapModal = useRef();
|
|
111
|
+
const lastIndexIntent = useRef({});
|
|
105
112
|
|
|
106
113
|
snapModal.current = async (index, force) => {
|
|
114
|
+
if (disabled && !force) return;
|
|
115
|
+
|
|
107
116
|
if (sizingReadyCaller.current.callback) {
|
|
108
117
|
if (index && unMountChildrenWhenClosed && !releaseUnmount)
|
|
109
118
|
setReleaseUnmount(true);
|
|
119
|
+
const intent =
|
|
120
|
+
lastIndexIntent.current[index] === undefined
|
|
121
|
+
? (lastIndexIntent.current[index] = 0)
|
|
122
|
+
: ++lastIndexIntent.current[index];
|
|
123
|
+
|
|
110
124
|
await sizingReadyCaller.current.promise;
|
|
125
|
+
if (lastIndexIntent.current[index] !== intent) return;
|
|
111
126
|
}
|
|
112
127
|
|
|
113
|
-
if (disabled && !force) return;
|
|
114
128
|
if (index && !sheetRef.current && !autoIndexModal) {
|
|
115
129
|
setAutoIndexModal(index);
|
|
116
130
|
} else if (sheetRef.current) {
|
|
@@ -173,12 +187,13 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
173
187
|
|
|
174
188
|
const centeredStyle = useMemo(() => centered ? ({
|
|
175
189
|
position: 'absolute',
|
|
176
|
-
width: viewWidth
|
|
190
|
+
width: viewWidth,
|
|
177
191
|
left: 0,
|
|
178
|
-
top: 0
|
|
179
|
-
marginTop: -(contentHeight / 2) || 0
|
|
192
|
+
top: 0
|
|
180
193
|
}) : undefined, [centered, contentHeight, viewWidth]);
|
|
181
194
|
|
|
195
|
+
const inputIdIterator = useRef(0);
|
|
196
|
+
|
|
182
197
|
const renderChild = () =>
|
|
183
198
|
<View
|
|
184
199
|
style={styling.absoluteFill}
|
|
@@ -197,39 +212,56 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
197
212
|
}} />
|
|
198
213
|
)}
|
|
199
214
|
<ReactHijacker
|
|
200
|
-
doHijack={
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (typeof thatRef === 'function') {
|
|
216
|
-
thatRef(r);
|
|
217
|
-
} else if (thatRef) thatRef.current = r;
|
|
215
|
+
doHijack={node => {
|
|
216
|
+
// if (node?.props?.dodge_keyboard_scan_off)
|
|
217
|
+
// return createHijackedElement(node);
|
|
218
|
+
if (!isDodgeInput(node)) return;
|
|
219
|
+
|
|
220
|
+
const renderer = () => {
|
|
221
|
+
const inputId = useMemo(() => `${++inputIdIterator.current}`, []);
|
|
222
|
+
|
|
223
|
+
const newProps = {
|
|
224
|
+
...node.props,
|
|
225
|
+
ref: r => {
|
|
226
|
+
if (r) {
|
|
227
|
+
inputRefs.current[inputId] = r;
|
|
228
|
+
} else if (inputRefs.current[inputId]) {
|
|
229
|
+
delete inputRefs.current[inputId];
|
|
218
230
|
}
|
|
231
|
+
|
|
232
|
+
const thatRef = node.props?.ref;
|
|
233
|
+
if (typeof thatRef === 'function') {
|
|
234
|
+
thatRef(r);
|
|
235
|
+
} else if (thatRef) thatRef.current = r;
|
|
219
236
|
}
|
|
220
237
|
};
|
|
238
|
+
|
|
239
|
+
return cloneElement(node, newProps);
|
|
221
240
|
}
|
|
241
|
+
|
|
242
|
+
return createHijackedElement(
|
|
243
|
+
<__HijackNode>
|
|
244
|
+
{renderer}
|
|
245
|
+
</__HijackNode>
|
|
246
|
+
);
|
|
222
247
|
}}>
|
|
223
248
|
<SnapSheet
|
|
224
249
|
{...restProps}
|
|
250
|
+
minSnapIndex={0}
|
|
225
251
|
ref={sheetRef}
|
|
226
252
|
snapPoints={snapPoints}
|
|
227
|
-
{...hasClosed ? { keyboardDodgingBehaviour: 'off' } : {}}
|
|
228
253
|
{...centered ? {
|
|
229
254
|
style: CenteredSheetStyle,
|
|
230
|
-
renderHandle: null
|
|
255
|
+
renderHandle: null,
|
|
256
|
+
...isLift ? {
|
|
257
|
+
__checkIfElementIsFocused: (r, refs) => {
|
|
258
|
+
const realChecker = restProps?.__checkIfElementIsFocused;
|
|
259
|
+
return !!r?.[CheckFocusedNode] && refs.some(v => realChecker ? realChecker?.(v) : v?.isFocused?.());
|
|
260
|
+
},
|
|
261
|
+
keyboardDodgingBehaviour: 'optimum'
|
|
262
|
+
} : {}
|
|
231
263
|
} : {}}
|
|
232
|
-
|
|
264
|
+
{...hasClosed ? { keyboardDodgingBehaviour: 'off' } : {}}
|
|
233
265
|
initialSnapIndex={Math.min(ModalState.indexOf(currentState), centered ? 1 : 2)}
|
|
234
266
|
disabled={centered || disabled || disablePanGesture}
|
|
235
267
|
onSnapFinish={i => {
|
|
@@ -240,14 +272,34 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
240
272
|
}}>
|
|
241
273
|
{(hasClosed && (!releaseUnmount && unMountChildrenWhenClosed))
|
|
242
274
|
? null :
|
|
243
|
-
<View
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
275
|
+
<View
|
|
276
|
+
style={centered ? centeredStyle : styling.flexer}
|
|
277
|
+
onLayout={e => {
|
|
278
|
+
if (centered) {
|
|
279
|
+
const h = e.nativeEvent.layout.height;
|
|
280
|
+
|
|
281
|
+
if (contentHeight === undefined)
|
|
282
|
+
return setContentHeight(PixelRatio.roundToNearestPixel(h));
|
|
283
|
+
|
|
284
|
+
const pixel = PixelRatio.roundToNearestPixel(h);
|
|
285
|
+
|
|
286
|
+
if (Math.abs(contentHeight - pixel) >= 1)
|
|
287
|
+
setContentHeight(pixel);
|
|
288
|
+
}
|
|
289
|
+
}}>
|
|
290
|
+
<View style={centered ? restProps.style : styling.flexer}>
|
|
249
291
|
{children}
|
|
250
292
|
</View>
|
|
293
|
+
{isLift ?
|
|
294
|
+
<View
|
|
295
|
+
ref={r => {
|
|
296
|
+
if (r) {
|
|
297
|
+
r[CheckFocusedNode] = true;
|
|
298
|
+
}
|
|
299
|
+
}}
|
|
300
|
+
dodge_keyboard_input
|
|
301
|
+
style={styling.fakePlaceholder}
|
|
302
|
+
/> : null}
|
|
251
303
|
</View>}
|
|
252
304
|
</SnapSheet>
|
|
253
305
|
</ReactHijacker>
|
|
@@ -257,15 +309,15 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
|
|
|
257
309
|
const flatStyle = StyleSheet.flatten(containerStyle);
|
|
258
310
|
|
|
259
311
|
return {
|
|
312
|
+
zIndex: hasClosed ? -99 : isNumber(flatStyle?.zIndex) ? flatStyle?.zIndex : 9999,
|
|
313
|
+
elevation: hasClosed ? 0 : isNumber(flatStyle?.elevation) ? flatStyle?.elevation : 9999,
|
|
314
|
+
...hasClosed ? { opacity: 0, display: 'none' } : {},
|
|
260
315
|
...flatStyle,
|
|
261
316
|
position: 'absolute',
|
|
262
317
|
width: '100%',
|
|
263
318
|
height: '100%',
|
|
264
319
|
top: 0,
|
|
265
|
-
left: 0
|
|
266
|
-
zIndex: hasClosed ? -99 : isNumber(flatStyle?.zIndex) ? flatStyle?.zIndex : 9999,
|
|
267
|
-
elevation: hasClosed ? 0 : isNumber(flatStyle?.elevation) ? flatStyle?.elevation : 9999,
|
|
268
|
-
...hasClosed ? { opacity: 0 } : {}
|
|
320
|
+
left: 0
|
|
269
321
|
};
|
|
270
322
|
}, [containerStyle, hasClosed]);
|
|
271
323
|
|
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;
|