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 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.1",
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.1",
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, Keyboard, PanResponder, StyleSheet, useAnimatedValue, View } from "react-native";
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', // off, whole
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
- useMemo(() => {
34
- if (snapPoints.length < 2) throw new Error('snapPoints must have at least two items');
35
- snapPoints.forEach((v, i, a) => {
36
- if (typeof v !== 'number' || !isNumber(v))
37
- throw new Error(`snapPoints must have a valid number but got ${v} at position ${i}`);
38
- if (i !== a.length - 1 && v >= a[i + 1])
39
- throw new Error(`snapPoints must be in accending order but got ${v} before ${a[i + 1]}`);
40
- });
41
- if (!Number.isInteger(initialSnapIndex) || initialSnapIndex < 0)
42
- throw new Error(`initialSnapIndex should be a positive integer but got:${initialSnapIndex}`);
43
- if (initialSnapIndex >= snapPoints.length) throw new Error(`initialSnapIndex is out of range`);
44
- }, snapPoints);
45
- const initSnapPoints = snapPoints;
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
- const extraLift = dodgeOffset && ((isOptimum ? dodgeOffset : isLift ? requiredLift : 0) || 0);
66
+ snapPoints = snapPoints.map(v => v + dodgeOffset);
67
+ const snapPointsKey = `${snapPoints}`;
55
68
 
56
- snapPoints = snapPoints.map(v => v + extraLift);
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 MAX_HEIGHT = snapPoints.slice(-1)[0];
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 => MAX_HEIGHT - h), snapPoints);
85
+ const snapTranslateValues = useMemo(() => snapPoints.map(h => h - MODAL_HEIGHT), [snapPointsKey]);
61
86
 
62
- const translateY = useAnimatedValue(snapTranslateValues[initialSnapIndex]);
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(translateY._value);
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 = MAX_HEIGHT - translateY._value;
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 = (index, force, velocity, onFinish) => {
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
- translateY.setValue(lastOffset.current);
125
+ bottomY.setValue(lastOffset.current);
114
126
 
115
- const prevY = translateY._value;
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(translateY, {
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
- }), snapPoints);
165
+ }));
154
166
 
155
167
  useEffect(() => {
156
- snapToIndex(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
157
- }, snapPoints);
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 && gesture.dy > 1) // If sheet is expanded & ScrollView is at top → capture downward drags
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 (newY < 0) return; // prevent overscrolling upward
176
- if (newY > MAX_HEIGHT) return;
193
+ if (
194
+ newY < snapTranslateValues[__loosenMinSnap ? 0 : minSnapIndex] ||
195
+ newY > snapTranslateValues.slice(-1)[0]
196
+ ) return;
177
197
 
178
- translateY.setValue(newY);
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, ...snapPoints]);
212
-
213
- const conStyle = useMemo(() => ({
214
- position: "absolute",
215
- width: "100%",
216
- backgroundColor: "#fff",
217
- borderTopLeftRadius: 25,
218
- borderTopRightRadius: 25,
219
- zIndex: 1,
220
- ...StyleSheet.flatten(style),
221
- bottom: 0,
222
- height: MAX_HEIGHT,
223
- transform: [{ translateY }]
224
- }), [MAX_HEIGHT, style]);
225
-
226
- useEffect(() => updatePrefferAnchor, [currentAnchorId]);
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
- if (directAnchor) return setPrefferedAnchor(directAnchor[0]);
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']) return { element: node };
330
+ if (node?.props?.['snap_sheet_scan_off'] || node?.props?.__checking_snap_scrollable) return;
331
+ if (!isScrollable(node)) return;
308
332
 
309
- if (isScrollable(node)) {
310
- const instanceId = path.join(',');
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
- return {
325
- props: {
326
- ...node?.props,
327
- ...disableDodging ? {} : { ['dodge_keyboard_scrollable']: true },
328
- ref: r => {
329
- if (r) {
330
- initNode();
331
- // if (scrollRefObj.current[instanceId].ref !== r) scheduleAnchorUpdate();
332
- scrollRefObj.current[instanceId].ref = r;
333
- } else if (scrollRefObj.current[instanceId]) {
334
- delete scrollRefObj.current[instanceId];
335
- scheduleAnchorUpdate();
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
  });
@@ -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 [-(contentHeight / 2), viewHeight / 2];
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 || 0,
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={(node, path) => {
201
- if (isDodgeInput(node)) {
202
- const inputId = path.join('=>');
203
-
204
- return {
205
- props: {
206
- ...node.props,
207
- ref: r => {
208
- if (r) {
209
- inputRefs.current[inputId] = r;
210
- } else if (inputRefs.current[inputId]) {
211
- delete inputRefs.current[inputId];
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
- __shaky_sheet={centered}
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 style={centered ? centeredStyle : styling.flexer}>
244
- <View
245
- style={centered ? restProps.style : styling.flexer}
246
- onLayout={e => {
247
- if (centered) setContentHeight(e.nativeEvent.layout.height);
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;