react-native-snap-sheet 1.0.3 → 1.0.5

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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-snap-sheet",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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.3",
22
+ "react-native-dodge-keyboard": "^1.0.5",
23
23
  "react-native-push-back": "^1.0.0"
24
24
  }
25
25
  }
package/src/snapsheet.js CHANGED
@@ -20,13 +20,12 @@ const SnapSheet = forwardRef(function SnapSheet({
20
20
  renderHandle,
21
21
  handleColor,
22
22
  keyboardDodgingBehaviour = 'optimum',
23
- keyboardDodgingOffset = 10,
23
+ keyboardDodgingOffset,
24
24
  children,
25
25
  disabled,
26
26
  currentAnchorId,
27
27
  __checkIfElementIsFocused,
28
- __loosenMinSnap,
29
- __shaky_sheet
28
+ __loosenMinSnap
30
29
  }, ref) {
31
30
  const isLift = keyboardDodgingBehaviour === 'whole';
32
31
 
@@ -53,9 +52,20 @@ const SnapSheet = forwardRef(function SnapSheet({
53
52
  if (__checkIfElementIsFocused !== undefined && typeof __checkIfElementIsFocused !== 'function')
54
53
  throw `expected '__checkIfElementIsFocused' to be a function but got ${__checkIfElementIsFocused}`;
55
54
 
56
- if (isLift) __checkIfElementIsFocused = r => !!r?.[CheckFocusedNode];
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
+ }
57
66
 
58
67
  const flattenStyle = StyleSheet.flatten(style) || {};
68
+ const initSnapPoints = snapPoints;
59
69
 
60
70
  const [scrollEnabled, setScrollEnabled] = useState(false);
61
71
  const [dodgeOffset, setDodgeOffset] = useState(0);
@@ -82,15 +92,15 @@ const SnapSheet = forwardRef(function SnapSheet({
82
92
  ? PotentialHeight
83
93
  : snapPoints.slice(-1)[0] - snapPoints[0];
84
94
 
85
- const snapTranslateValues = useMemo(() => snapPoints.map(h => h - MODAL_HEIGHT), [snapPointsKey]);
95
+ const snapTranslateValues = useMemo(() => snapPoints.map(h => MODAL_HEIGHT - h), [snapPointsKey]);
86
96
 
87
- const bottomY = useAnimatedValue(snapTranslateValues[initialSnapIndex]);
97
+ const translateY = useAnimatedValue(snapTranslateValues[initialSnapIndex]);
88
98
 
89
99
  /**
90
100
  * @type {import("react").RefObject<{[key: string]: { ref: import('react-native').ScrollView, scrollY: 0, location: number[], anchorId: boolean }}>}
91
101
  */
92
102
  const scrollRefObj = useRef({});
93
- const lastOffset = useRef(bottomY._value);
103
+ const lastOffset = useRef(translateY._value);
94
104
  const lastSnapIndex = useRef(initialSnapIndex);
95
105
  const instantPrefferAnchor = useRef();
96
106
  instantPrefferAnchor.current = prefferedAnchor;
@@ -102,7 +112,7 @@ const SnapSheet = forwardRef(function SnapSheet({
102
112
  instantScrollEnabled.current = scrollEnabled;
103
113
 
104
114
  const getCurrentSnap = (draggingUpward) => {
105
- const shownHeight = bottomY._value + MODAL_HEIGHT;
115
+ const shownHeight = MODAL_HEIGHT - translateY._value;
106
116
  const currentSnapIndex = draggingUpward ? snapPoints.findIndex((v, i, a) => v <= shownHeight && (i === a.length - 1 || shownHeight < a[i + 1]))
107
117
  : snapPoints.findIndex((v, i, a) => v >= shownHeight && (!i || shownHeight > a[i - 1]));
108
118
 
@@ -121,10 +131,7 @@ const SnapSheet = forwardRef(function SnapSheet({
121
131
 
122
132
  const newY = snapTranslateValues[index];
123
133
 
124
- if (__shaky_sheet && lastOffset.current !== newY)
125
- bottomY.setValue(lastOffset.current);
126
-
127
- const prevY = bottomY._value;
134
+ const prevY = translateY._value;
128
135
  setScrollEnabled(index === snapPoints.length - 1);
129
136
  setCurrentIndex(index);
130
137
 
@@ -141,9 +148,9 @@ const SnapSheet = forwardRef(function SnapSheet({
141
148
 
142
149
  const timer = setTimeout(guessFinish, Math.max(300, timeout));
143
150
 
144
- // console.log('snapTimer:', { timeout, pixel });
151
+ // console.log('snapTimer:', { timeout, pixel }, ' newY:', newY, ' snapPoint:', initSnapPoints, ' snapTrans:', snapTranslateValues);
145
152
 
146
- Animated.spring(bottomY, {
153
+ Animated.spring(translateY, {
147
154
  velocity,
148
155
  toValue: newY,
149
156
  useNativeDriver: true
@@ -165,7 +172,7 @@ const SnapSheet = forwardRef(function SnapSheet({
165
172
  }));
166
173
 
167
174
  useEffect(() => {
168
- snapToIndex.current(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
175
+ if (!isLifting.current) snapToIndex.current(Math.min(lastSnapIndex.current, snapPoints.length - 1), true);
169
176
  }, [snapPointsKey]);
170
177
 
171
178
  const panResponder = useMemo(() => {
@@ -191,11 +198,11 @@ const SnapSheet = forwardRef(function SnapSheet({
191
198
  const newY = gesture.dy + lastOffset.current;
192
199
 
193
200
  if (
194
- newY < snapTranslateValues[__loosenMinSnap ? 0 : minSnapIndex] ||
195
- newY > snapTranslateValues.slice(-1)[0]
201
+ newY > snapTranslateValues[__loosenMinSnap ? 0 : minSnapIndex] ||
202
+ newY < snapTranslateValues.slice(-1)[0]
196
203
  ) return;
197
204
 
198
- bottomY.setValue(newY);
205
+ translateY.setValue(newY);
199
206
  },
200
207
  onPanResponderRelease: (_, gesture) => {
201
208
  const { dy, vy } = gesture; // when vy is lesser, it is scroll up
@@ -234,15 +241,13 @@ const SnapSheet = forwardRef(function SnapSheet({
234
241
  const { height, minHeight, maxHeight, ...rest } = flattenStyle;
235
242
 
236
243
  return {
237
- position: "absolute",
238
- width: "100%",
239
244
  backgroundColor: "#fff",
240
245
  borderTopLeftRadius: 25,
241
246
  borderTopRightRadius: 25,
242
247
  zIndex: 1,
243
248
  height: (Object.hasOwn(flattenStyle, 'height') && height === undefined) ? undefined : MODAL_HEIGHT,
244
249
  ...rest,
245
- bottom: bottomY
250
+ transform: [{ translateY }]
246
251
  };
247
252
  }, [snapPointsKey, style]);
248
253
 
@@ -303,97 +308,139 @@ const SnapSheet = forwardRef(function SnapSheet({
303
308
  const sameIndex = currentIndex === finishedIndex;
304
309
 
305
310
  const instanceIdIterator = useRef(0);
311
+ const prevKE = useRef();
312
+ const prevLiftOffset = useRef(0);
313
+ const isLifting = useRef();
314
+
315
+ const quicklyDodgeKeyboard = (offset, keyboardEvent) => {
316
+ // console.log('quicklyDodgeKeyboard offset:', offset, ' keyboardEvent:', keyboardEvent);
317
+
318
+ if (!keyboardEvent) {
319
+ if (!(keyboardEvent = prevKE.current)) {
320
+ prevLiftOffset.current = offset;
321
+ return;
322
+ }
323
+ }
324
+ if (keyboardEvent.endCoordinates.height) {
325
+ prevKE.current = keyboardEvent;
326
+ } else {
327
+ if (prevKE.current) keyboardEvent = prevKE.current;
328
+ }
329
+
330
+ if (prevLiftOffset.current === offset) return;
331
+ prevLiftOffset.current = offset;
332
+
333
+ const newPosY = MODAL_HEIGHT - (initSnapPoints[lastSnapIndex.current] + offset);
334
+ const newDuration = (Math.abs(newPosY - translateY._value) * keyboardEvent.duration) / keyboardEvent?.endCoordinates.height;
335
+
336
+ // console.log('newPosY:', newPosY, ' timing newDuration:', newDuration);
337
+ isLifting.current = true;
338
+ Animated.timing(translateY, {
339
+ duration: Math.max((newDuration || 0) - 70, 0),
340
+ toValue: newPosY,
341
+ useNativeDriver: false
342
+ }).start(() => {
343
+ if (offset === prevLiftOffset.current) {
344
+ isLifting.current = false;
345
+ setDodgeOffset(offset);
346
+ }
347
+ });
348
+ }
306
349
 
307
350
  return (
308
351
  <View style={styling.absoluteFill}>
309
- <Animated.View
310
- style={conStyle}
311
- {...panResponder.panHandlers}>
312
- {doRendable?.(
313
- renderHandle,
314
- <View style={styling.modalHandle}>
315
- <View style={handleDotStyle} />
316
- </View>
317
- )}
318
- <View style={styling.flexer}>
319
- <DodgeKeyboard
320
- offset={keyboardDodgingOffset}
321
- disabled={!sameIndex || disableDodging}
322
- checkIfElementIsFocused={__checkIfElementIsFocused}
323
- onHandleDodging={({ liftUp }) => {
324
- setDodgeOffset(liftUp);
325
- }}>
326
- {ReactHijacker({
327
- children,
328
- enableLocator: true,
329
- doHijack: (node, path) => {
330
- if (node?.props?.['snap_sheet_scan_off'] || node?.props?.__checking_snap_scrollable) return;
331
- if (!isScrollable(node)) return;
332
-
333
- const renderer = () => {
334
- const instanceId = useMemo(() => `${++instanceIdIterator.current}`, []);
335
-
336
- const initNode = () => {
337
- if (!scrollRefObj.current[instanceId])
338
- scrollRefObj.current[instanceId] = { scrollY: 0, location: path };
339
- const thisAnchorId = node.props?.snap_sheet_scroll_anchor;
340
-
341
- if (scrollRefObj.current[instanceId].anchorId !== thisAnchorId) {
342
- scheduleAnchorUpdate(300);
352
+ <View style={{ position: 'absolute', bottom: 0, width: '100%' }}>
353
+ <Animated.View
354
+ style={conStyle}
355
+ {...panResponder.panHandlers}>
356
+ {doRendable?.(
357
+ renderHandle,
358
+ <View style={styling.modalHandle}>
359
+ <View style={handleDotStyle} />
360
+ </View>
361
+ )}
362
+ <View style={styling.flexer}>
363
+ <DodgeKeyboard
364
+ offset={keyboardDodgingOffset}
365
+ disabled={!sameIndex || disableDodging}
366
+ checkIfElementIsFocused={__checkIfElementIsFocused}
367
+ onHandleDodging={({ liftUp, keyboardEvent }) => {
368
+ quicklyDodgeKeyboard(liftUp, keyboardEvent);
369
+ }}>
370
+ {ReactHijacker({
371
+ children,
372
+ enableLocator: true,
373
+ doHijack: (node, path) => {
374
+ if (node?.props?.snap_sheet_scan_off || node?.props?.__checking_snap_scrollable)
375
+ return createHijackedElement(node);
376
+
377
+ if (!isScrollable(node)) return;
378
+
379
+ const renderer = () => {
380
+ const instanceId = useMemo(() => `${++instanceIdIterator.current}`, []);
381
+
382
+ const initNode = () => {
383
+ if (!scrollRefObj.current[instanceId])
384
+ scrollRefObj.current[instanceId] = { scrollY: 0, location: path };
385
+ const thisAnchorId = node.props?.snap_sheet_scroll_anchor;
386
+
387
+ if (scrollRefObj.current[instanceId].anchorId !== thisAnchorId) {
388
+ scheduleAnchorUpdate(300);
389
+ }
390
+ scrollRefObj.current[instanceId].anchorId = thisAnchorId;
343
391
  }
344
- scrollRefObj.current[instanceId].anchorId = thisAnchorId;
345
- }
346
- initNode();
347
-
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();
392
+ initNode();
393
+
394
+ const newProps = {
395
+ ...node?.props,
396
+ __checking_snap_scrollable: true,
397
+ ...disableDodging ? {} : { ['dodge_keyboard_scrollable']: true },
398
+ ref: r => {
399
+ if (r) {
400
+ initNode();
401
+ // if (scrollRefObj.current[instanceId].ref !== r) scheduleAnchorUpdate();
402
+ scrollRefObj.current[instanceId].ref = r;
403
+ } else if (scrollRefObj.current[instanceId]) {
404
+ delete scrollRefObj.current[instanceId];
405
+ scheduleAnchorUpdate();
406
+ }
407
+
408
+ const thatRef = node.props?.ref;
409
+ if (typeof thatRef === 'function') {
410
+ thatRef(r);
411
+ } else if (thatRef) thatRef.current = r;
412
+ },
413
+ ...prefferedAnchor === instanceId ? { scrollEnabled } : {},
414
+ onScroll: (e) => {
415
+ onAnchorScroll(e, instanceId);
416
+ return node.props?.onScroll?.(e);
360
417
  }
418
+ };
361
419
 
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);
371
- }
372
- };
420
+ return cloneElement(node, newProps);
421
+ }
373
422
 
374
- return cloneElement(node, newProps);
423
+ return createHijackedElement(
424
+ <__HijackNode>
425
+ {renderer}
426
+ </__HijackNode>
427
+ );
375
428
  }
376
-
377
- return createHijackedElement(
378
- <__HijackNode>
379
- {renderer}
380
- </__HijackNode>
381
- );
382
- }
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}
394
- </DodgeKeyboard>
395
- </View>
396
- </Animated.View>
429
+ })}
430
+ {isLift ?
431
+ <View
432
+ ref={r => {
433
+ if (r) {
434
+ r[CheckFocusedNode] = true;
435
+ }
436
+ }}
437
+ dodge_keyboard_input
438
+ style={styling.fakePlaceholder}
439
+ /> : null}
440
+ </DodgeKeyboard>
441
+ </View>
442
+ </Animated.View>
443
+ </View>
397
444
  </View>
398
445
  );
399
446
  });
@@ -8,7 +8,7 @@ import { styling } from "./styling";
8
8
  import SnapSheet from "./snapsheet";
9
9
 
10
10
  const ModalState = ['closed', 'middle', 'opened'];
11
- const CenteredSheetStyle = { width: 0, height: undefined };
11
+ const CenteredSheetStyle = { width: 0, height: 0 };
12
12
  const CheckFocusedNode = '__fakeSnapSheetModalFocused';
13
13
 
14
14
  export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
@@ -40,6 +40,9 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
40
40
  }
41
41
  }, [centered]);
42
42
 
43
+ if (restProps.__checkIfElementIsFocused !== undefined && typeof restProps.__checkIfElementIsFocused !== 'function')
44
+ throw `expected '__checkIfElementIsFocused' to be a function but got ${restProps.__checkIfElementIsFocused}`;
45
+
43
46
  if (centered) {
44
47
  middleHeight = undefined;
45
48
  if (initialState === 'middle') initialState = 'opened';
@@ -105,15 +108,23 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
105
108
  const sheetRef = useRef();
106
109
  const inputRefs = useRef({});
107
110
  const snapModal = useRef();
111
+ const lastIndexIntent = useRef({});
108
112
 
109
113
  snapModal.current = async (index, force) => {
114
+ if (disabled && !force) return;
115
+
110
116
  if (sizingReadyCaller.current.callback) {
111
117
  if (index && unMountChildrenWhenClosed && !releaseUnmount)
112
118
  setReleaseUnmount(true);
119
+ const intent =
120
+ lastIndexIntent.current[index] === undefined
121
+ ? (lastIndexIntent.current[index] = 0)
122
+ : ++lastIndexIntent.current[index];
123
+
113
124
  await sizingReadyCaller.current.promise;
125
+ if (lastIndexIntent.current[index] !== intent) return;
114
126
  }
115
127
 
116
- if (disabled && !force) return;
117
128
  if (index && !sheetRef.current && !autoIndexModal) {
118
129
  setAutoIndexModal(index);
119
130
  } else if (sheetRef.current) {
@@ -202,6 +213,8 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
202
213
  )}
203
214
  <ReactHijacker
204
215
  doHijack={node => {
216
+ // if (node?.props?.dodge_keyboard_scan_off)
217
+ // return createHijackedElement(node);
205
218
  if (!isDodgeInput(node)) return;
206
219
 
207
220
  const renderer = () => {
@@ -240,9 +253,11 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
240
253
  {...centered ? {
241
254
  style: CenteredSheetStyle,
242
255
  renderHandle: null,
243
- __shaky_sheet: true,
244
256
  ...isLift ? {
245
- __checkIfElementIsFocused: r => !!r?.[CheckFocusedNode],
257
+ __checkIfElementIsFocused: (r, refs) => {
258
+ const realChecker = restProps?.__checkIfElementIsFocused;
259
+ return !!r?.[CheckFocusedNode] && refs.some(v => realChecker ? realChecker?.(v) : v?.isFocused?.());
260
+ },
246
261
  keyboardDodgingBehaviour: 'optimum'
247
262
  } : {}
248
263
  } : {}}
@@ -294,15 +309,15 @@ export const SnapSheetModalBase = forwardRef(function SnapSheetModalBase({
294
309
  const flatStyle = StyleSheet.flatten(containerStyle);
295
310
 
296
311
  return {
312
+ zIndex: hasClosed ? -99 : 9999,
313
+ elevation: hasClosed ? 0 : 9999,
314
+ ...hasClosed ? { opacity: 0 } : {},
297
315
  ...flatStyle,
298
316
  position: 'absolute',
299
317
  width: '100%',
300
318
  height: '100%',
301
319
  top: 0,
302
- left: 0,
303
- zIndex: hasClosed ? -99 : isNumber(flatStyle?.zIndex) ? flatStyle?.zIndex : 9999,
304
- elevation: hasClosed ? 0 : isNumber(flatStyle?.elevation) ? flatStyle?.elevation : 9999,
305
- ...hasClosed ? { opacity: 0 } : {}
320
+ left: 0
306
321
  };
307
322
  }, [containerStyle, hasClosed]);
308
323