react-native-smooth-clip-view 0.4.6 → 0.5.1

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +47 -0
  3. package/android/src/main/cpp/SmoothClipAndroid.h +22 -0
  4. package/android/src/main/cpp/SmoothClipBindings.cpp +37 -3
  5. package/android/src/main/cpp/SmoothClipRegistry.cpp +85 -2
  6. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +18 -0
  7. package/android/src/main/java/com/smoothclipview/SmoothClipBackdropView.kt +59 -0
  8. package/android/src/main/java/com/smoothclipview/SmoothClipBackdropViewManager.kt +63 -0
  9. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +15 -0
  10. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +316 -34
  11. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +19 -0
  12. package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +1 -1
  13. package/cpp/SmoothClipAnimationCurve.h +36 -11
  14. package/cpp/SmoothClipRegistry.h +11 -4
  15. package/ios/SmoothClipBackdropView.h +17 -0
  16. package/ios/SmoothClipBackdropView.mm +267 -0
  17. package/ios/SmoothClipRegistry.mm +192 -9
  18. package/ios/SmoothClipTurboModule.cpp +33 -11
  19. package/ios/SmoothClipView.mm +723 -22
  20. package/ios/SmoothClipViewRegistry.h +14 -0
  21. package/lib/module/SmoothClipBackdropView.js +40 -0
  22. package/lib/module/SmoothClipBackdropView.js.map +1 -0
  23. package/lib/module/SmoothClipBackdropViewNativeComponent.ts +15 -0
  24. package/lib/module/SmoothClipView.ios.js +17 -1
  25. package/lib/module/SmoothClipView.ios.js.map +1 -1
  26. package/lib/module/SmoothClipViewNativeComponent.ts +4 -0
  27. package/lib/module/geometry.js +44 -6
  28. package/lib/module/geometry.js.map +1 -1
  29. package/lib/module/groups.native.js +10 -3
  30. package/lib/module/groups.native.js.map +1 -1
  31. package/lib/module/index.js +1 -0
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/presentationCodec.js +6 -2
  34. package/lib/module/presentationCodec.js.map +1 -1
  35. package/lib/typescript/src/SmoothClipBackdropView.d.ts +155 -0
  36. package/lib/typescript/src/SmoothClipBackdropView.d.ts.map +1 -0
  37. package/lib/typescript/src/SmoothClipBackdropViewNativeComponent.d.ts +8 -0
  38. package/lib/typescript/src/SmoothClipBackdropViewNativeComponent.d.ts.map +1 -0
  39. package/lib/typescript/src/SmoothClipView.android.d.ts +1 -1
  40. package/lib/typescript/src/SmoothClipView.android.d.ts.map +1 -1
  41. package/lib/typescript/src/SmoothClipView.d.ts +1 -1
  42. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  43. package/lib/typescript/src/SmoothClipView.ios.d.ts +15 -1
  44. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  45. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +4 -0
  46. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  47. package/lib/typescript/src/geometry.d.ts +19 -0
  48. package/lib/typescript/src/geometry.d.ts.map +1 -1
  49. package/lib/typescript/src/groups.native.d.ts.map +1 -1
  50. package/lib/typescript/src/index.d.ts +4 -2
  51. package/lib/typescript/src/index.d.ts.map +1 -1
  52. package/lib/typescript/src/presentationCodec.d.ts +1 -1
  53. package/lib/typescript/src/presentationCodec.d.ts.map +1 -1
  54. package/package.json +4 -1
  55. package/src/SmoothClipBackdropView.tsx +53 -0
  56. package/src/SmoothClipBackdropViewNativeComponent.ts +15 -0
  57. package/src/SmoothClipView.android.tsx +4 -1
  58. package/src/SmoothClipView.ios.tsx +24 -1
  59. package/src/SmoothClipView.tsx +4 -1
  60. package/src/SmoothClipViewNativeComponent.ts +4 -0
  61. package/src/geometry.ts +59 -4
  62. package/src/groups.native.ts +9 -3
  63. package/src/index.ts +7 -1
  64. package/src/presentationCodec.ts +8 -2
package/src/geometry.ts CHANGED
@@ -53,6 +53,17 @@ export type CanonicalClipGeometry = Readonly<{
53
53
  curve: ClipCurve;
54
54
  }>;
55
55
 
56
+ /**
57
+ * Translation, in host points, of every `SmoothClipBackdropView` bound to the
58
+ * clip's controller. It rides the same native run and `setFrame` stream as
59
+ * the aperture, so content that must stay locked to the window (a canvas
60
+ * centred in it) is sampled by the same clock as the clip.
61
+ */
62
+ export type ClipBackdrop = Readonly<{
63
+ translateX: number;
64
+ translateY: number;
65
+ }>;
66
+
56
67
  export type SmoothClipPresentation = Readonly<{
57
68
  clip: ClipGeometry;
58
69
  contentTranslateX: number;
@@ -64,6 +75,8 @@ export type SmoothClipPresentation = Readonly<{
64
75
  /** Opacity of the complete clipped object; defaults to 1. */
65
76
  opacity?: number;
66
77
  boxShadow?: ClipBoxShadow;
78
+ /** Defaults to no translation when omitted. */
79
+ backdrop?: ClipBackdrop;
67
80
  }>;
68
81
 
69
82
  export type CanonicalSmoothClipPresentation = Readonly<{
@@ -74,6 +87,7 @@ export type CanonicalSmoothClipPresentation = Readonly<{
74
87
  rotation: `${number}rad`;
75
88
  opacity: number;
76
89
  boxShadow?: CanonicalClipBoxShadow;
90
+ backdrop: ClipBackdrop;
77
91
  }>;
78
92
 
79
93
  /** Numeric native angle. Invalid input remains NaN for atomic validation. */
@@ -96,6 +110,19 @@ function resolvedOpacity(presentation: SmoothClipPresentation): number {
96
110
  }
97
111
 
98
112
  const DEFAULT_SHADOW_COLOR = '#000000ff';
113
+ const NO_BACKDROP: ClipBackdrop = { translateX: 0, translateY: 0 };
114
+
115
+ function resolvedBackdrop(presentation: SmoothClipPresentation): ClipBackdrop {
116
+ 'worklet';
117
+ return presentation.backdrop ?? NO_BACKDROP;
118
+ }
119
+
120
+ function isFiniteBackdrop(backdrop: ClipBackdrop): boolean {
121
+ 'worklet';
122
+ return (
123
+ Number.isFinite(backdrop.translateX) && Number.isFinite(backdrop.translateY)
124
+ );
125
+ }
99
126
 
100
127
  function isClipCurve(curve: ClipCurve | undefined): boolean {
101
128
  'worklet';
@@ -247,6 +274,7 @@ export function isFiniteClipPresentation(
247
274
  contentScale > 0 &&
248
275
  Number.isFinite(rotationRadians(presentation.rotation)) &&
249
276
  Number.isFinite(presentation.opacity ?? 1) &&
277
+ isFiniteBackdrop(resolvedBackdrop(presentation)) &&
250
278
  shadow !== null
251
279
  );
252
280
  }
@@ -264,6 +292,10 @@ export function clipPresentationEquals(
264
292
  resolvedContentScale(first) === resolvedContentScale(second) &&
265
293
  rotationRadians(first.rotation) === rotationRadians(second.rotation) &&
266
294
  resolvedOpacity(first) === resolvedOpacity(second) &&
295
+ resolvedBackdrop(first).translateX ===
296
+ resolvedBackdrop(second).translateX &&
297
+ resolvedBackdrop(first).translateY ===
298
+ resolvedBackdrop(second).translateY &&
267
299
  clipBoxShadowEquals(first.boxShadow, second.boxShadow)
268
300
  );
269
301
  }
@@ -340,25 +372,47 @@ export function canonicalizeClipGeometry(
340
372
  );
341
373
  }
342
374
 
375
+ /**
376
+ * Validates and canonicalizes in one pass: this runs on the UI runtime for
377
+ * every `setFrame` of a drag, so the geometry is checked once, the shadow
378
+ * colour is parsed once and the rotation is parsed once. The result is null
379
+ * exactly when `isFiniteClipPresentation` is false.
380
+ */
343
381
  export function canonicalizeClipPresentation(
344
382
  presentation: SmoothClipPresentation
345
383
  ): CanonicalSmoothClipPresentation | null {
346
384
  'worklet';
347
- if (!isFiniteClipPresentation(presentation)) return null;
348
-
349
385
  const clip = canonicalizeClipGeometry(presentation.clip);
350
386
  if (clip === null) return null;
351
387
  const boxShadow = canonicalizeClipBoxShadow(presentation.boxShadow);
352
388
  if (boxShadow === null) return null;
389
+ const contentScale = resolvedContentScale(presentation);
390
+ const rotation = rotationRadians(presentation.rotation);
391
+ const backdrop = resolvedBackdrop(presentation);
392
+ if (
393
+ !Number.isFinite(presentation.contentTranslateX) ||
394
+ !Number.isFinite(presentation.contentTranslateY) ||
395
+ !Number.isFinite(contentScale) ||
396
+ contentScale <= 0 ||
397
+ !Number.isFinite(rotation) ||
398
+ !Number.isFinite(presentation.opacity ?? 1) ||
399
+ !isFiniteBackdrop(backdrop)
400
+ ) {
401
+ return null;
402
+ }
353
403
 
354
404
  return {
355
405
  clip,
356
406
  contentTranslateX: presentation.contentTranslateX,
357
407
  contentTranslateY: presentation.contentTranslateY,
358
- contentScale: resolvedContentScale(presentation),
359
- rotation: `${rotationRadians(presentation.rotation)}rad`,
408
+ contentScale,
409
+ rotation: `${rotation}rad`,
360
410
  opacity: resolvedOpacity(presentation),
361
411
  ...(boxShadow === undefined ? {} : { boxShadow }),
412
+ backdrop:
413
+ backdrop === NO_BACKDROP
414
+ ? NO_BACKDROP
415
+ : { translateX: backdrop.translateX, translateY: backdrop.translateY },
362
416
  };
363
417
  }
364
418
 
@@ -394,6 +448,7 @@ export function createClipPresentation(
394
448
  contentScale,
395
449
  rotation: '0rad',
396
450
  opacity: 1,
451
+ backdrop: NO_BACKDROP,
397
452
  ...(canonicalShadow === undefined
398
453
  ? {}
399
454
  : {
@@ -1,5 +1,4 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import { Platform } from 'react-native';
3
2
  import { scheduleOnRN, scheduleOnUI } from 'react-native-worklets';
4
3
  import type {
5
4
  ClipReduceMotion,
@@ -44,7 +43,6 @@ const cancelAnimationGroupHostFunction =
44
43
 
45
44
  const SNAPSHOT_STRIDE = PRESENTATION_STRIDE + 1;
46
45
  const MOTION_ENTRY_STRIDE = PRESENTATION_STRIDE * 2 + 2;
47
- const NEEDS_START_STAMP = Platform.OS === 'android';
48
46
  const DEFAULT_MASS = 4;
49
47
  const DEFAULT_STIFFNESS = 900;
50
48
  const DEFAULT_DAMPING = 120;
@@ -177,9 +175,17 @@ function reduceMotionCode(value: ClipReduceMotion | undefined): number {
177
175
  return 0;
178
176
  }
179
177
 
178
+ /**
179
+ * The Reanimated frame stamp (`__frameTimestamp`, milliseconds) when the run
180
+ * starts inside a UI frame, else the animation clock. Both platforms anchor
181
+ * the native run to it: the Android frame loop advances from it, and iOS uses
182
+ * it as the shared Core Animation `beginTime`. A run and a `withTiming` begun
183
+ * in the same UI frame therefore share one epoch. Reanimated stamps a frame
184
+ * with the display link's target vsync, so an install-time
185
+ * `CACurrentMediaTime()` would put the native run about one frame ahead.
186
+ */
180
187
  function startTimestamp(): number {
181
188
  'worklet';
182
- if (!NEEDS_START_STAMP) return Number.NaN;
183
189
  const runtime = globalThis as {
184
190
  __frameTimestamp?: number;
185
191
  _getAnimationTimestamp?: () => number;
package/src/index.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  export { SmoothClipView } from './SmoothClipView';
2
- export type { SmoothClipViewProps } from './SmoothClipView';
2
+ export type {
3
+ SmoothClipShadowRendering,
4
+ SmoothClipViewProps,
5
+ } from './SmoothClipView';
6
+ export { SmoothClipBackdropView } from './SmoothClipBackdropView';
7
+ export type { SmoothClipBackdropViewProps } from './SmoothClipBackdropView';
3
8
  export { useSmoothClipController } from './controllers';
4
9
  export { useSmoothClipGroup } from './groups';
5
10
  export { getSmoothClipCapabilities } from './capabilities';
@@ -36,6 +41,7 @@ export type {
36
41
  CanonicalClipGeometry,
37
42
  CanonicalClipBoxShadow,
38
43
  CanonicalSmoothClipPresentation,
44
+ ClipBackdrop,
39
45
  ClipCurve,
40
46
  ClipRotation,
41
47
  ClipGeometry,
@@ -5,7 +5,7 @@ import type { CanonicalSmoothClipPresentation } from './geometry';
5
5
  * Versionless native packet layout. Keep this as the single JS codec used by
6
6
  * drivers, groups, snapshots, and cancellation.
7
7
  */
8
- export const PRESENTATION_STRIDE = 23;
8
+ export const PRESENTATION_STRIDE = 25;
9
9
 
10
10
  export function appendPresentationPacket(
11
11
  values: number[],
@@ -43,7 +43,9 @@ export function appendPresentationPacket(
43
43
  boxShadow?.blurRadius ?? 0,
44
44
  boxShadow?.spreadDistance ?? 0,
45
45
  Number(presentation.rotation.slice(0, -3)),
46
- presentation.opacity
46
+ presentation.opacity,
47
+ presentation.backdrop.translateX,
48
+ presentation.backdrop.translateY
47
49
  );
48
50
  }
49
51
 
@@ -125,6 +127,10 @@ export function presentationFromPacket(
125
127
  contentScale,
126
128
  rotation: `${values[offset + 21] as number}rad`,
127
129
  opacity: values[offset + 22] as number,
130
+ backdrop: {
131
+ translateX: values[offset + 23] as number,
132
+ translateY: values[offset + 24] as number,
133
+ },
128
134
  ...(shadowEnabled
129
135
  ? {
130
136
  boxShadow: {