@momo-kits/foundation 0.164.1-beta.4 → 0.164.1-beta.6

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.
@@ -335,11 +335,20 @@ const useNativeCanGoBack = () => {
335
335
  };
336
336
 
337
337
  /**
338
- * finger travel that reads as "swipe back recognised". flat px on purpose:
339
- * scaling it by screen width would move the tick to a different physical
340
- * thumb distance on every device
338
+ * fraction of screen width at which releasing commits the pop. deliberately
339
+ * pinned to react-navigation's own rule: Card.tsx (GestureState.END,
340
+ * @react-navigation/stack 7.4.2) closes the card when
341
+ * `(translation + velocity * gestureVelocityImpact) * inverted > distance / 2`
342
+ * with `distance = layout.width` for a horizontal gesture, and exposes no
343
+ * option to override it. so the tick means "release now and you go back"
344
+ * instead of marking a distance of its own — keep the two in sync.
345
+ *
346
+ * mid drag only translation is known, velocity exists solely at release, so a
347
+ * short fast flick can commit without ever crossing this and pops with no
348
+ * tick. that asymmetry is intended: modelling velocity per frame would tick
349
+ * on swipes that then do not commit, which is the worse error
341
350
  */
342
- const SWIPE_BACK_HAPTIC_THRESHOLD = 100;
351
+ const SWIPE_BACK_HAPTIC_COMMIT_RATIO = 0.5;
343
352
 
344
353
  const readAnimatedValue = (node: unknown, fallback: number): number => {
345
354
  const getValue = (node as { __getValue?: () => unknown } | undefined)
@@ -371,8 +380,8 @@ const getSwipeGestureValue = (
371
380
  };
372
381
 
373
382
  /**
374
- * one light tick once the swipe back has travelled far enough to read as
375
- * recognised. iOS only, mirroring `gestureEnabled` in getStackOptions.
383
+ * one light tick once the swipe back passes the point where releasing commits
384
+ * the pop. iOS only, mirroring `gestureEnabled` in getStackOptions.
376
385
  *
377
386
  * both listeners are installed on mount and never lazily inside the gesture:
378
387
  * on the New Architecture `addListener` queues `startListeningToAnimatedNodeValue`
@@ -401,7 +410,7 @@ const useSwipeBackHaptic = () => {
401
410
  return;
402
411
  }
403
412
 
404
- const { swiping, inverted, current } = animation;
413
+ const { swiping, inverted, current, layouts } = animation;
405
414
  const gesture = getSwipeGestureValue(current?.progress);
406
415
  if (!gesture || typeof swiping?.addListener !== 'function') {
407
416
  return;
@@ -420,19 +429,52 @@ const useSwipeBackHaptic = () => {
420
429
  */
421
430
  const direction = readAnimatedValue(inverted, 1);
422
431
 
432
+ /**
433
+ * plain JS numbers, not animated nodes (`StackCardInterpolationProps`).
434
+ * screen layout can still be unmeasured on an early render, and a zero
435
+ * width would put the threshold at 0 and tick the moment the finger moves
436
+ */
437
+ const commitThreshold =
438
+ (layouts?.screen?.width ?? 0) * SWIPE_BACK_HAPTIC_COMMIT_RATIO;
439
+
440
+ /**
441
+ * TEMPORARY DIAGNOSTIC (DS-760) — remove once the swipe-back no-pop is understood.
442
+ *
443
+ * The test device cannot run Xcode, so this borrows the one channel proven to reach it.
444
+ * On release past the commit threshold react-navigation should pop; if this screen is
445
+ * still mounted shortly after, the pop was requested and then swallowed — `Card` clears
446
+ * its 32ms `pendingGestureCallback` from `animate()`, and `componentDidUpdate` re-enters
447
+ * `animate()` for any re-render in that window.
448
+ *
449
+ * A `heavy` tick after release therefore means "gesture completed, pop lost". Silence
450
+ * means the gesture never reached the threshold, or was cancelled before END.
451
+ */
452
+ let lastTravel = 0;
453
+ let pendingPopProbe: ReturnType<typeof setTimeout> | undefined;
454
+
423
455
  const swipingListener = swiping.addListener(({ value }) => {
456
+ const wasSwiping = isSwiping;
424
457
  isSwiping = value === 1;
425
- if (!isSwiping) {
426
- hasFired.current = false;
458
+ if (isSwiping) {
459
+ return;
427
460
  }
461
+ hasFired.current = false;
462
+ if (wasSwiping && commitThreshold > 0 && lastTravel >= commitThreshold) {
463
+ pendingPopProbe = setTimeout(() => {
464
+ navigator?.maxApi?.triggerEventVibration?.('heavy');
465
+ }, 500);
466
+ }
467
+ lastTravel = 0;
428
468
  });
429
469
 
430
470
  const gestureListener = gesture.addListener(({ value: translation }) => {
431
- if (!isSwiping) {
471
+ if (!isSwiping || commitThreshold <= 0) {
432
472
  return;
433
473
  }
434
474
 
435
- if (translation * direction < SWIPE_BACK_HAPTIC_THRESHOLD) {
475
+ lastTravel = translation * direction;
476
+
477
+ if (lastTravel < commitThreshold) {
436
478
  hasFired.current = false;
437
479
  return;
438
480
  }
@@ -444,6 +486,10 @@ const useSwipeBackHaptic = () => {
444
486
  });
445
487
 
446
488
  return () => {
489
+ // unmount cancels the probe: the screen popped, which is the success case
490
+ if (pendingPopProbe !== undefined) {
491
+ clearTimeout(pendingPopProbe);
492
+ }
447
493
  gesture.removeListener(gestureListener);
448
494
  swiping.removeListener(swipingListener);
449
495
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.164.1-beta.4",
3
+ "version": "0.164.1-beta.6",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},