react-native-snap-sheet 1.0.2 → 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 +139 -105
- 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
|
|
|
@@ -178,10 +190,12 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
178
190
|
onPanResponderMove: (_, gesture) => {
|
|
179
191
|
const newY = gesture.dy + lastOffset.current;
|
|
180
192
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
193
|
+
if (
|
|
194
|
+
newY < snapTranslateValues[__loosenMinSnap ? 0 : minSnapIndex] ||
|
|
195
|
+
newY > snapTranslateValues.slice(-1)[0]
|
|
196
|
+
) return;
|
|
183
197
|
|
|
184
|
-
|
|
198
|
+
bottomY.setValue(newY);
|
|
185
199
|
},
|
|
186
200
|
onPanResponderRelease: (_, gesture) => {
|
|
187
201
|
const { dy, vy } = gesture; // when vy is lesser, it is scroll up
|
|
@@ -196,7 +210,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
196
210
|
vy > 0.3 ? 0 : currentSnapIndex;
|
|
197
211
|
const willFullyShow = newSnapIndex === snapPoints.length - 1;
|
|
198
212
|
|
|
199
|
-
snapToIndex(newSnapIndex, true, draggingUpward ? vy : undefined);
|
|
213
|
+
snapToIndex.current(newSnapIndex, true, draggingUpward ? vy : undefined);
|
|
200
214
|
|
|
201
215
|
// Only scroll if there was a fling velocity upward
|
|
202
216
|
if (inheritScrollVelocityOnExpand && willFullyShow && vy < -0.1) {
|
|
@@ -214,22 +228,24 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
});
|
|
217
|
-
}, [!disabled,
|
|
218
|
-
|
|
219
|
-
const conStyle = useMemo(() =>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
|
|
233
249
|
const updateAnchorReducer = useRef();
|
|
234
250
|
|
|
235
251
|
const scheduleAnchorUpdate = (timeout = 100) => {
|
|
@@ -242,6 +258,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
242
258
|
const directAnchor = rankedAnchors.find(v => v[1].anchorId === currentAnchorId);
|
|
243
259
|
setPrefferedAnchor(directAnchor?.[0]);
|
|
244
260
|
}
|
|
261
|
+
useEffect(updatePrefferAnchor, [currentAnchorId]);
|
|
245
262
|
|
|
246
263
|
const onAnchorScroll = (e, instanceId) => {
|
|
247
264
|
const scrollY = e.nativeEvent.contentOffset.y;
|
|
@@ -273,7 +290,7 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
273
290
|
|
|
274
291
|
const currentSnapIndex = getCurrentSnap(false);
|
|
275
292
|
const newSnapIndex = snapWhileDecelerating ? Math.max(0, currentSnapIndex - 1) : 0;
|
|
276
|
-
snapToIndex(newSnapIndex, false, -scrollVelocity);
|
|
293
|
+
snapToIndex.current(newSnapIndex, false, -scrollVelocity);
|
|
277
294
|
}
|
|
278
295
|
}
|
|
279
296
|
|
|
@@ -285,6 +302,8 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
285
302
|
const disableDodging = keyboardDodgingBehaviour === 'off';
|
|
286
303
|
const sameIndex = currentIndex === finishedIndex;
|
|
287
304
|
|
|
305
|
+
const instanceIdIterator = useRef(0);
|
|
306
|
+
|
|
288
307
|
return (
|
|
289
308
|
<View style={styling.absoluteFill}>
|
|
290
309
|
<Animated.View
|
|
@@ -300,16 +319,19 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
300
319
|
<DodgeKeyboard
|
|
301
320
|
offset={keyboardDodgingOffset}
|
|
302
321
|
disabled={!sameIndex || disableDodging}
|
|
322
|
+
checkIfElementIsFocused={__checkIfElementIsFocused}
|
|
303
323
|
onHandleDodging={({ liftUp }) => {
|
|
304
324
|
setDodgeOffset(liftUp);
|
|
305
325
|
}}>
|
|
306
326
|
{ReactHijacker({
|
|
307
327
|
children,
|
|
328
|
+
enableLocator: true,
|
|
308
329
|
doHijack: (node, path) => {
|
|
309
|
-
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;
|
|
310
332
|
|
|
311
|
-
|
|
312
|
-
const instanceId =
|
|
333
|
+
const renderer = () => {
|
|
334
|
+
const instanceId = useMemo(() => `${++instanceIdIterator.current}`, []);
|
|
313
335
|
|
|
314
336
|
const initNode = () => {
|
|
315
337
|
if (!scrollRefObj.current[instanceId])
|
|
@@ -323,43 +345,55 @@ const SnapSheet = forwardRef(function SnapSheet({
|
|
|
323
345
|
}
|
|
324
346
|
initNode();
|
|
325
347
|
|
|
326
|
-
|
|
327
|
-
props
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const thatRef = node.props?.ref;
|
|
341
|
-
if (typeof thatRef === 'function') {
|
|
342
|
-
thatRef(r);
|
|
343
|
-
} else if (thatRef) thatRef.current = r;
|
|
344
|
-
},
|
|
345
|
-
...prefferedAnchor === instanceId ? { scrollEnabled } : {},
|
|
346
|
-
onScroll: (e) => {
|
|
347
|
-
onAnchorScroll(e, instanceId);
|
|
348
|
-
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();
|
|
349
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);
|
|
350
371
|
}
|
|
351
|
-
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return cloneElement(node, newProps);
|
|
352
375
|
}
|
|
376
|
+
|
|
377
|
+
return createHijackedElement(
|
|
378
|
+
<__HijackNode>
|
|
379
|
+
{renderer}
|
|
380
|
+
</__HijackNode>
|
|
381
|
+
);
|
|
353
382
|
}
|
|
354
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}
|
|
355
394
|
</DodgeKeyboard>
|
|
356
395
|
</View>
|
|
357
396
|
</Animated.View>
|
|
358
|
-
{isLift ?
|
|
359
|
-
<View
|
|
360
|
-
ref={bottomFakePlaceholderRef}
|
|
361
|
-
style={styling.fakePlaceholder}
|
|
362
|
-
onLayout={updateKeyboardOffset} /> : null}
|
|
363
397
|
</View>
|
|
364
398
|
);
|
|
365
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;
|