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.
- package/CHANGELOG.md +69 -0
- package/README.md +47 -0
- package/android/src/main/cpp/SmoothClipAndroid.h +22 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +37 -3
- package/android/src/main/cpp/SmoothClipRegistry.cpp +85 -2
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +18 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBackdropView.kt +59 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBackdropViewManager.kt +63 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +15 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +316 -34
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +19 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +1 -1
- package/cpp/SmoothClipAnimationCurve.h +36 -11
- package/cpp/SmoothClipRegistry.h +11 -4
- package/ios/SmoothClipBackdropView.h +17 -0
- package/ios/SmoothClipBackdropView.mm +267 -0
- package/ios/SmoothClipRegistry.mm +192 -9
- package/ios/SmoothClipTurboModule.cpp +33 -11
- package/ios/SmoothClipView.mm +723 -22
- package/ios/SmoothClipViewRegistry.h +14 -0
- package/lib/module/SmoothClipBackdropView.js +40 -0
- package/lib/module/SmoothClipBackdropView.js.map +1 -0
- package/lib/module/SmoothClipBackdropViewNativeComponent.ts +15 -0
- package/lib/module/SmoothClipView.ios.js +17 -1
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +4 -0
- package/lib/module/geometry.js +44 -6
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groups.native.js +10 -3
- package/lib/module/groups.native.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/presentationCodec.js +6 -2
- package/lib/module/presentationCodec.js.map +1 -1
- package/lib/typescript/src/SmoothClipBackdropView.d.ts +155 -0
- package/lib/typescript/src/SmoothClipBackdropView.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipBackdropViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/SmoothClipBackdropViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.android.d.ts +1 -1
- package/lib/typescript/src/SmoothClipView.android.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +15 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/geometry.d.ts +19 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groups.native.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +1 -1
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/SmoothClipBackdropView.tsx +53 -0
- package/src/SmoothClipBackdropViewNativeComponent.ts +15 -0
- package/src/SmoothClipView.android.tsx +4 -1
- package/src/SmoothClipView.ios.tsx +24 -1
- package/src/SmoothClipView.tsx +4 -1
- package/src/SmoothClipViewNativeComponent.ts +4 -0
- package/src/geometry.ts +59 -4
- package/src/groups.native.ts +9 -3
- package/src/index.ts +7 -1
- 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
|
|
359
|
-
rotation: `${
|
|
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
|
: {
|
package/src/groups.native.ts
CHANGED
|
@@ -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 {
|
|
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,
|
package/src/presentationCodec.ts
CHANGED
|
@@ -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 =
|
|
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: {
|