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 CHANGED
@@ -86,7 +86,7 @@ export interface SnapSheetBaseProps {
86
86
  /**
87
87
  * Additional offset to add when dodging keyboard
88
88
  *
89
- * @default 10
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.2",
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.2",
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, 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,47 +19,80 @@ const SnapSheet = forwardRef(function SnapSheet({
17
19
  inheritScrollVelocityOnCollapse,
18
20
  renderHandle,
19
21
  handleColor,
20
- keyboardDodgingBehaviour = 'optimum', // off, whole
21
- keyboardDodgingOffset = 10,
22
+ keyboardDodgingBehaviour = 'optimum',
23
+ keyboardDodgingOffset,
22
24
  children,
23
25
  disabled,
24
26
  currentAnchorId,
25
- __shaky_sheet
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
- 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);
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
- const extraLift = dodgeOffset && ((isOptimum ? dodgeOffset : isLift ? requiredLift : 0) || 0);
76
+ snapPoints = snapPoints.map(v => v + dodgeOffset);
77
+ const snapPointsKey = `${snapPoints}`;
55
78
 
56
- snapPoints = snapPoints.map(v => v + extraLift);
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 MAX_HEIGHT = snapPoints.slice(-1)[0];
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 => MAX_HEIGHT - h), snapPoints);
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 = MAX_HEIGHT - translateY._value;
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 = (index, force, velocity, onFinish) => {
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
- const newY = snapTranslateValues[index];
130
+ if (index < minSnapIndex) index = minSnapIndex;
111
131
 
112
- if (__shaky_sheet && lastOffset.current !== newY)
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
- }), snapPoints);
172
+ }));
154
173
 
155
174
  useEffect(() => {
156
- snapToIndex(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
157
- }, snapPoints);
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 (newY < 0) return; // prevent overscrolling upward
182
- if (newY > MAX_HEIGHT) return;
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, ...snapPoints]);
218
-
219
- const conStyle = useMemo(() => ({
220
- position: "absolute",
221
- width: "100%",
222
- backgroundColor: "#fff",
223
- borderTopLeftRadius: 25,
224
- borderTopRightRadius: 25,
225
- zIndex: 1,
226
- ...StyleSheet.flatten(style),
227
- bottom: 0,
228
- height: MAX_HEIGHT,
229
- transform: [{ translateY }]
230
- }), [MAX_HEIGHT, style]);
231
-
232
- useEffect(() => updatePrefferAnchor, [currentAnchorId]);
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
- <Animated.View
291
- style={conStyle}
292
- {...panResponder.panHandlers}>
293
- {doRendable?.(
294
- renderHandle,
295
- <View style={styling.modalHandle}>
296
- <View style={handleDotStyle} />
297
- </View>
298
- )}
299
- <View style={styling.flexer}>
300
- <DodgeKeyboard
301
- offset={keyboardDodgingOffset}
302
- disabled={!sameIndex || disableDodging}
303
- onHandleDodging={({ liftUp }) => {
304
- setDodgeOffset(liftUp);
305
- }}>
306
- {ReactHijacker({
307
- children,
308
- doHijack: (node, path) => {
309
- if (node?.props?.['snap_sheet_scan_off']) return { element: node };
310
-
311
- if (isScrollable(node)) {
312
- const instanceId = path.join(',');
313
-
314
- const initNode = () => {
315
- if (!scrollRefObj.current[instanceId])
316
- scrollRefObj.current[instanceId] = { scrollY: 0, location: path };
317
- const thisAnchorId = node.props?.snap_sheet_scroll_anchor;
318
-
319
- if (scrollRefObj.current[instanceId].anchorId !== thisAnchorId) {
320
- scheduleAnchorUpdate(300);
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
- scrollRefObj.current[instanceId].anchorId = thisAnchorId;
323
- }
324
- initNode();
378
+ initNode();
325
379
 
326
- return {
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
- </DodgeKeyboard>
356
- </View>
357
- </Animated.View>
358
- {isLift ?
359
- <View
360
- ref={bottomFakePlaceholderRef}
361
- style={styling.fakePlaceholder}
362
- onLayout={updateKeyboardOffset} /> : null}
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
  });
@@ -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 [-(contentHeight / 2), viewHeight / 2];
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 || 0,
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={(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;
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
- __shaky_sheet={centered}
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 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
- }}>
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;