react-native-smooth-clip-view 0.2.6 → 0.2.7

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.
@@ -48,6 +48,14 @@ export function detachDriverState(state: DriverState): void {
48
48
  if (statesById.get(state.driverId) === state) {
49
49
  statesById.delete(state.driverId);
50
50
  }
51
+ // Requests pending at teardown were settled by rejectDriverRequests, but
52
+ // their completion deferrals can no longer be released: the late-scheduled
53
+ // resolver's releaseDriverCompletions no-ops against a detached state. An
54
+ // effect replay reattaches THIS same object, so a stale positive count
55
+ // would queue every future completion forever. The deferrals died with
56
+ // their requests — reset them.
57
+ state.completionDeferrals = 0;
58
+ state.deferredCompletions.length = 0;
51
59
  }
52
60
 
53
61
  export function createDriverState(
@@ -107,9 +107,19 @@ function animationIsFinite(
107
107
  return false;
108
108
  }
109
109
  if (animation.type === 'timing') {
110
+ const [x1, , x2] = animation.controlPoints;
110
111
  return (
111
112
  Number.isFinite(animation.duration) &&
112
- animation.controlPoints.every(Number.isFinite)
113
+ animation.controlPoints.every(Number.isFinite) &&
114
+ // A cubic-bezier easing is only defined for x within [0,1] (the CSS /
115
+ // CoreAnimation / Reanimated Easing.bezier contract). Out-of-range x
116
+ // makes the Android parameter solve meaningless while CoreAnimation
117
+ // silently clamps — reject like any other invalid input instead of
118
+ // diverging per platform.
119
+ x1 >= 0 &&
120
+ x1 <= 1 &&
121
+ x2 >= 0 &&
122
+ x2 <= 1
113
123
  );
114
124
  }
115
125
  if (animation.type === 'spring') {
@@ -322,6 +332,10 @@ export function useSmoothClipDriver(
322
332
  animation: SmoothClipAnimation
323
333
  ): number => {
324
334
  'worklet';
335
+ // Cleanup owns the terminal state. Check it before validation so even an
336
+ // invalid stale call cannot mint a native rejection id/completion against
337
+ // a driver whose listener and registry entry are already gone.
338
+ if (disposed.value !== 0) return 0;
325
339
  if (
326
340
  !isFiniteClipPresentation(target) ||
327
341
  !animationIsFinite(target, animation)
@@ -334,8 +348,6 @@ export function useSmoothClipDriver(
334
348
  // rejectAnimation, which would mint an id and a completion for a driver
335
349
  // whose JS state is already detached. This is the "unsupported dispatch"
336
350
  // arm of the documented 0 contract.
337
- if (disposed.value !== 0) return 0;
338
-
339
351
  const from = animation.from;
340
352
  if (from !== undefined) {
341
353
  // Fused hot write: exactly setScalars(from…) issued immediately
@@ -475,8 +487,10 @@ export function useSmoothClipDriver(
475
487
  // dispatch. A missing driver is accepted when this call carries the
476
488
  // authoritative interactive start above; a start-less missing-state
477
489
  // request remains unsupported rather than inventing zero geometry.
478
- // Validation failures never get here (they reject before any side
479
- // effect with a fresh id and one finished:false completion).
490
+ // Validation failures never get here: they reject before any side
491
+ // effect, with a fresh id and one finished:false completion once the
492
+ // native entry exists — in the pre-seed window rejectAnimation has no
493
+ // entry and returns the bare 0 sentinel with no completion instead.
480
494
  ownership.value = INTERACTIVE;
481
495
  presentation.value = start;
482
496
  if (scalarsWereStale) scalarsStale.value = 1;
@@ -568,7 +582,14 @@ export function useSmoothClipDriver(
568
582
  return promise;
569
583
  },
570
584
  set(next) {
571
- const { requestId, promise } = createReactRequest<void>(driverId);
585
+ // Teardown resolves (undefined) rather than rejects: `set` is a
586
+ // documented fire-and-forget call, and a promise discarded with
587
+ // `void` must not become an unhandled rejection at unmount.
588
+ const { requestId, promise } = createReactRequest<void>(
589
+ driverId,
590
+ false,
591
+ { value: undefined }
592
+ );
572
593
  scheduleOnUI(() => {
573
594
  'worklet';
574
595
  setOnUI(next);
@@ -577,9 +598,12 @@ export function useSmoothClipDriver(
577
598
  return promise;
578
599
  },
579
600
  animateTo(target, animation) {
601
+ // Teardown resolves with the documented 0 rejection sentinel (see
602
+ // `set` above): the README's own example voids this promise.
580
603
  const { requestId, promise } = createReactRequest<number>(
581
604
  driverId,
582
- true
605
+ true,
606
+ { value: 0 }
583
607
  );
584
608
  scheduleOnUI(() => {
585
609
  'worklet';
@@ -6,6 +6,11 @@ import {
6
6
  type PendingRequest = {
7
7
  resolve(value: unknown): void;
8
8
  reject(error: Error): void;
9
+ // Set for calls whose documented usage is fire-and-forget (`void
10
+ // driver.react.animateTo(...)`): teardown resolves them with this benign
11
+ // sentinel instead of rejecting, so a discarded promise cannot surface as
12
+ // an unhandled rejection during ordinary unmount.
13
+ teardownResolution?: { value: unknown };
9
14
  };
10
15
 
11
16
  const requests = new Map<number, PendingRequest>();
@@ -14,7 +19,8 @@ let nextRequestId = 0;
14
19
 
15
20
  export function createReactRequest<T>(
16
21
  driverId: number,
17
- deferCompletions = false
22
+ deferCompletions = false,
23
+ teardownResolution?: { value: unknown }
18
24
  ): { requestId: number; promise: Promise<T> } {
19
25
  nextRequestId = (nextRequestId % 0x7ffffffe) + 1;
20
26
  const requestId = nextRequestId;
@@ -27,6 +33,7 @@ export function createReactRequest<T>(
27
33
  requests.set(requestId, {
28
34
  resolve: resolveRequest as (value: unknown) => void,
29
35
  reject: rejectRequest,
36
+ teardownResolution,
30
37
  });
31
38
  const driverRequests = requestsByDriver.get(driverId) ?? new Set<number>();
32
39
  driverRequests.add(requestId);
@@ -58,7 +65,12 @@ export function rejectDriverRequests(driverId: number): void {
58
65
  for (const requestId of driverRequests) {
59
66
  const request = requests.get(requestId);
60
67
  requests.delete(requestId);
61
- request?.reject(new Error('[SmoothClipView] Driver was destroyed.'));
68
+ if (!request) continue;
69
+ if (request.teardownResolution) {
70
+ request.resolve(request.teardownResolution.value);
71
+ } else {
72
+ request.reject(new Error('[SmoothClipView] Driver was destroyed.'));
73
+ }
62
74
  }
63
75
  requestsByDriver.delete(driverId);
64
76
  }