react-native-smooth-clip-view 0.3.0 → 0.4.0
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/README.md +132 -388
- package/android/build.gradle +3 -3
- package/android/proguard-rules.pro +2 -2
- package/android/src/main/cpp/SmoothClipAndroid.h +9 -12
- package/android/src/main/cpp/SmoothClipBindings.cpp +300 -704
- package/android/src/main/cpp/SmoothClipRegistry.cpp +348 -594
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +36 -227
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +16 -19
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +36 -294
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +344 -488
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +75 -77
- package/cpp/SmoothClipAnimationCurve.h +237 -346
- package/cpp/SmoothClipRegistry.h +24 -52
- package/cpp/SmoothClipSharedGeometry.h +64 -94
- package/cpp/SmoothClipVelocityTracker.h +5 -5
- package/ios/SmoothClipGeometry.h +23 -34
- package/ios/SmoothClipRegistry.mm +206 -711
- package/ios/SmoothClipTurboModule.cpp +217 -715
- package/ios/SmoothClipTurboModule.h +54 -297
- package/ios/SmoothClipView.mm +599 -505
- package/ios/SmoothClipViewRegistry.h +2 -7
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +84 -17
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -1
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +20 -26
- package/lib/module/capabilities.native.js +1 -19
- package/lib/module/capabilities.native.js.map +1 -1
- package/lib/module/capabilityTypes.js +0 -9
- package/lib/module/capabilityTypes.js.map +1 -1
- package/lib/module/controllerInternals.js +28 -0
- package/lib/module/controllerInternals.js.map +1 -0
- package/lib/module/controllerLifecycle.js +9 -0
- package/lib/module/controllerLifecycle.js.map +1 -0
- package/lib/module/controllerTypes.js +4 -0
- package/lib/module/controllerTypes.js.map +1 -0
- package/lib/module/controllers.android.js +4 -0
- package/lib/module/controllers.android.js.map +1 -0
- package/lib/module/controllers.ios.js +4 -0
- package/lib/module/controllers.ios.js.map +1 -0
- package/lib/module/controllers.js +4 -0
- package/lib/module/controllers.js.map +1 -0
- package/lib/module/controllers.native.js +89 -0
- package/lib/module/controllers.native.js.map +1 -0
- package/lib/module/geometry.js +71 -51
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/{groupDriverTypes.js.map → groupTypes.js.map} +1 -1
- package/lib/module/groups.android.js +4 -0
- package/lib/module/groups.android.js.map +1 -0
- package/lib/module/groups.ios.js +4 -0
- package/lib/module/groups.ios.js.map +1 -0
- package/lib/module/groups.js +4 -0
- package/lib/module/groups.js.map +1 -0
- package/lib/module/groups.native.js +399 -0
- package/lib/module/groups.native.js.map +1 -0
- package/lib/module/ids.js +8 -0
- package/lib/module/ids.js.map +1 -0
- package/lib/module/index.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeCompletion.js +37 -16
- package/lib/module/nativeCompletion.js.map +1 -1
- package/lib/module/presentationCodec.js +81 -0
- package/lib/module/presentationCodec.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +14 -23
- package/lib/typescript/src/NativeSmoothClipModule.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 +18 -14
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +10 -5
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.native.d.ts +1 -1
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -1
- package/lib/typescript/src/capabilityTypes.d.ts +0 -6
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -1
- package/lib/typescript/src/controllerInternals.d.ts +14 -0
- package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
- package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
- package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/controllerTypes.d.ts +62 -0
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllers.android.d.ts +2 -0
- package/lib/typescript/src/controllers.android.d.ts.map +1 -0
- package/lib/typescript/src/controllers.d.ts +2 -0
- package/lib/typescript/src/controllers.d.ts.map +1 -0
- package/lib/typescript/src/controllers.ios.d.ts +2 -0
- package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
- package/lib/typescript/src/controllers.native.d.ts +3 -0
- package/lib/typescript/src/controllers.native.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +22 -12
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupTypes.d.ts +31 -0
- package/lib/typescript/src/groupTypes.d.ts.map +1 -0
- package/lib/typescript/src/groups.android.d.ts +2 -0
- package/lib/typescript/src/groups.android.d.ts.map +1 -0
- package/lib/typescript/src/groups.d.ts +2 -0
- package/lib/typescript/src/groups.d.ts.map +1 -0
- package/lib/typescript/src/groups.ios.d.ts +2 -0
- package/lib/typescript/src/groups.ios.d.ts.map +1 -0
- package/lib/typescript/src/groups.native.d.ts +3 -0
- package/lib/typescript/src/groups.native.d.ts.map +1 -0
- package/lib/typescript/src/ids.d.ts +2 -0
- package/lib/typescript/src/ids.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -6
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +16 -6
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +10 -0
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/NativeSmoothClipModule.ts +41 -292
- package/src/SmoothClipView.ios.tsx +130 -23
- package/src/SmoothClipView.tsx +5 -1
- package/src/SmoothClipViewNativeComponent.ts +20 -26
- package/src/capabilities.native.ts +2 -52
- package/src/capabilityTypes.ts +0 -14
- package/src/controllerInternals.ts +52 -0
- package/src/controllerLifecycle.ts +6 -0
- package/src/controllerTypes.ts +87 -0
- package/src/controllers.android.ts +1 -0
- package/src/controllers.ios.ts +1 -0
- package/src/controllers.native.ts +104 -0
- package/src/controllers.ts +1 -0
- package/src/geometry.ts +118 -74
- package/src/groupTypes.ts +53 -0
- package/src/groups.android.ts +1 -0
- package/src/groups.ios.ts +1 -0
- package/src/groups.native.ts +604 -0
- package/src/groups.ts +1 -0
- package/src/ids.ts +6 -0
- package/src/index.ts +20 -28
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/driverState.js +0 -82
- package/lib/module/driverState.js.map +0 -1
- package/lib/module/driverTypes.js +0 -4
- package/lib/module/driverTypes.js.map +0 -1
- package/lib/module/drivers.android.js +0 -6
- package/lib/module/drivers.android.js.map +0 -1
- package/lib/module/drivers.ios.js +0 -6
- package/lib/module/drivers.ios.js.map +0 -1
- package/lib/module/drivers.js +0 -4
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -756
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/groupDriverTypes.js +0 -4
- package/lib/module/groupDrivers.android.js +0 -4
- package/lib/module/groupDrivers.android.js.map +0 -1
- package/lib/module/groupDrivers.ios.js +0 -4
- package/lib/module/groupDrivers.ios.js.map +0 -1
- package/lib/module/groupDrivers.js +0 -4
- package/lib/module/groupDrivers.js.map +0 -1
- package/lib/module/groupDrivers.native.js +0 -799
- package/lib/module/groupDrivers.native.js.map +0 -1
- package/lib/module/presentationProtocol.js +0 -15
- package/lib/module/presentationProtocol.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -26
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -121
- package/lib/typescript/src/driverTypes.d.ts.map +0 -1
- package/lib/typescript/src/drivers.android.d.ts +0 -3
- package/lib/typescript/src/drivers.android.d.ts.map +0 -1
- package/lib/typescript/src/drivers.d.ts +0 -3
- package/lib/typescript/src/drivers.d.ts.map +0 -1
- package/lib/typescript/src/drivers.ios.d.ts +0 -3
- package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/drivers.native.d.ts +0 -5
- package/lib/typescript/src/drivers.native.d.ts.map +0 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +0 -66
- package/lib/typescript/src/groupDriverTypes.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.android.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.android.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.ios.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.native.d.ts +0 -4
- package/lib/typescript/src/groupDrivers.native.d.ts.map +0 -1
- package/lib/typescript/src/presentationProtocol.d.ts +0 -4
- package/lib/typescript/src/presentationProtocol.d.ts.map +0 -1
- package/lib/typescript/src/reactRequests.d.ts +0 -9
- package/lib/typescript/src/reactRequests.d.ts.map +0 -1
- package/src/driverState.ts +0 -118
- package/src/driverTypes.ts +0 -171
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -1599
- package/src/drivers.ts +0 -6
- package/src/groupDriverTypes.ts +0 -117
- package/src/groupDrivers.android.ts +0 -1
- package/src/groupDrivers.ios.ts +0 -1
- package/src/groupDrivers.native.ts +0 -1189
- package/src/groupDrivers.ts +0 -1
- package/src/presentationProtocol.ts +0 -28
- package/src/reactRequests.ts +0 -76
|
@@ -1,68 +1,18 @@
|
|
|
1
1
|
import NativeSmoothClipModule from './smoothClipNative';
|
|
2
|
-
import {
|
|
3
|
-
SMOOTH_CLIP_V1_CAPABILITIES,
|
|
4
|
-
type SmoothClipCapabilities,
|
|
5
|
-
} from './capabilityTypes';
|
|
6
|
-
|
|
7
|
-
type VersionedModule = typeof NativeSmoothClipModule & {
|
|
8
|
-
getPresentationProtocolVersion?: () => number;
|
|
9
|
-
supportsAutonomousComplexPathAnimation?: () => boolean;
|
|
10
|
-
};
|
|
2
|
+
import type { SmoothClipCapabilities } from './capabilityTypes';
|
|
11
3
|
|
|
12
4
|
let cached: SmoothClipCapabilities | undefined;
|
|
13
5
|
|
|
14
6
|
export function getSmoothClipCapabilities(): SmoothClipCapabilities {
|
|
15
7
|
if (cached) return cached;
|
|
16
|
-
const module = NativeSmoothClipModule as VersionedModule;
|
|
17
|
-
let version = 1;
|
|
18
|
-
try {
|
|
19
|
-
version = module.getPresentationProtocolVersion?.() === 2 ? 2 : 1;
|
|
20
|
-
} catch {
|
|
21
|
-
version = 1;
|
|
22
|
-
}
|
|
23
|
-
if (version !== 2) {
|
|
24
|
-
cached = SMOOTH_CLIP_V1_CAPABILITIES;
|
|
25
|
-
return cached;
|
|
26
|
-
}
|
|
27
8
|
let autonomousComplexPathAnimation = false;
|
|
28
9
|
try {
|
|
29
10
|
autonomousComplexPathAnimation =
|
|
30
|
-
|
|
11
|
+
NativeSmoothClipModule.supportsAutonomousComplexPathAnimation() === true;
|
|
31
12
|
} catch {
|
|
32
13
|
autonomousComplexPathAnimation = false;
|
|
33
14
|
}
|
|
34
|
-
const groups =
|
|
35
|
-
typeof (
|
|
36
|
-
module as Partial<{
|
|
37
|
-
beginGroupInteractionV2: unknown;
|
|
38
|
-
snapshotGroupV2: unknown;
|
|
39
|
-
setClipPresentationBatchV2: unknown;
|
|
40
|
-
animateTimingGroupV2: unknown;
|
|
41
|
-
animateSpringGroupV2: unknown;
|
|
42
|
-
animateKeyframesGroupV2: unknown;
|
|
43
|
-
cancelAnimationGroupV2: unknown;
|
|
44
|
-
}>
|
|
45
|
-
).beginGroupInteractionV2 === 'function' &&
|
|
46
|
-
typeof (module as Partial<{ snapshotGroupV2: unknown }>).snapshotGroupV2 ===
|
|
47
|
-
'function' &&
|
|
48
|
-
typeof (module as Partial<{ setClipPresentationBatchV2: unknown }>)
|
|
49
|
-
.setClipPresentationBatchV2 === 'function' &&
|
|
50
|
-
typeof (module as Partial<{ animateTimingGroupV2: unknown }>)
|
|
51
|
-
.animateTimingGroupV2 === 'function' &&
|
|
52
|
-
typeof (module as Partial<{ animateSpringGroupV2: unknown }>)
|
|
53
|
-
.animateSpringGroupV2 === 'function' &&
|
|
54
|
-
typeof (module as Partial<{ animateKeyframesGroupV2: unknown }>)
|
|
55
|
-
.animateKeyframesGroupV2 === 'function' &&
|
|
56
|
-
typeof (module as Partial<{ cancelAnimationGroupV2: unknown }>)
|
|
57
|
-
.cancelAnimationGroupV2 === 'function' &&
|
|
58
|
-
typeof (module as Partial<{ onClipGroupAnimationComplete: unknown }>)
|
|
59
|
-
.onClipGroupAnimationComplete === 'function';
|
|
60
15
|
cached = {
|
|
61
|
-
presentationProtocolVersion: 2,
|
|
62
|
-
groups,
|
|
63
|
-
perCornerRadii: true,
|
|
64
|
-
continuousCurve: true,
|
|
65
|
-
contentScale: true,
|
|
66
16
|
autonomousComplexPathAnimation,
|
|
67
17
|
};
|
|
68
18
|
return cached;
|
package/src/capabilityTypes.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
export type SmoothClipCapabilities = Readonly<{
|
|
2
|
-
presentationProtocolVersion: 1 | 2;
|
|
3
|
-
groups: boolean;
|
|
4
|
-
perCornerRadii: boolean;
|
|
5
|
-
continuousCurve: boolean;
|
|
6
|
-
contentScale: boolean;
|
|
7
2
|
autonomousComplexPathAnimation: boolean;
|
|
8
3
|
}>;
|
|
9
|
-
|
|
10
|
-
export const SMOOTH_CLIP_V1_CAPABILITIES: SmoothClipCapabilities = {
|
|
11
|
-
presentationProtocolVersion: 1,
|
|
12
|
-
groups: false,
|
|
13
|
-
perCornerRadii: false,
|
|
14
|
-
continuousCurve: false,
|
|
15
|
-
contentScale: false,
|
|
16
|
-
autonomousComplexPathAnimation: false,
|
|
17
|
-
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { SmoothClipController, SmoothClipRef } from './controllerTypes';
|
|
2
|
+
import type { CanonicalSmoothClipPresentation } from './geometry';
|
|
3
|
+
|
|
4
|
+
export type InternalSmoothClipController = Readonly<{
|
|
5
|
+
ref: SmoothClipRef;
|
|
6
|
+
initialFrame: CanonicalSmoothClipPresentation;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
export type InternalSmoothClipRef = SmoothClipRef & Readonly<{ id: number }>;
|
|
10
|
+
|
|
11
|
+
const controllers = new WeakMap<
|
|
12
|
+
SmoothClipController,
|
|
13
|
+
InternalSmoothClipController
|
|
14
|
+
>();
|
|
15
|
+
|
|
16
|
+
export function setControllerRef(
|
|
17
|
+
controller: SmoothClipController,
|
|
18
|
+
value: InternalSmoothClipController
|
|
19
|
+
): void {
|
|
20
|
+
controllers.set(controller, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getControllerRef(
|
|
24
|
+
controller: SmoothClipController
|
|
25
|
+
): InternalSmoothClipController {
|
|
26
|
+
const value = controllers.get(controller);
|
|
27
|
+
if (!value) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'[SmoothClipView] Controllers must be created by useSmoothClipController.'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function unwrapSmoothClipRef(
|
|
36
|
+
ref: SmoothClipRef | undefined
|
|
37
|
+
): InternalSmoothClipRef | undefined {
|
|
38
|
+
'worklet';
|
|
39
|
+
const internal = ref as InternalSmoothClipRef | undefined;
|
|
40
|
+
if (
|
|
41
|
+
internal === undefined ||
|
|
42
|
+
!Number.isSafeInteger(internal.id) ||
|
|
43
|
+
internal.id <= 0
|
|
44
|
+
) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return internal;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createSmoothClipRef(id: number): SmoothClipRef {
|
|
51
|
+
return { id } as InternalSmoothClipRef;
|
|
52
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import NativeSmoothClipModule from './NativeSmoothClipModule';
|
|
2
|
+
|
|
3
|
+
/** Destroy from the React Native runtime; native forwards to the UI thread. */
|
|
4
|
+
export function destroyController(controllerId: number): void {
|
|
5
|
+
NativeSmoothClipModule.destroyDriver(controllerId);
|
|
6
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CanonicalSmoothClipPresentation,
|
|
3
|
+
ClipGeometry,
|
|
4
|
+
SmoothClipPresentation,
|
|
5
|
+
} from './geometry';
|
|
6
|
+
|
|
7
|
+
export type ClipReduceMotion = 'system' | 'always' | 'never';
|
|
8
|
+
|
|
9
|
+
type SmoothClipAnimationBase = Readonly<{
|
|
10
|
+
/** Resolved per transaction; it is never retained by the controller. */
|
|
11
|
+
reduceMotion?: ClipReduceMotion;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
export type TimingClipAnimation = SmoothClipAnimationBase &
|
|
15
|
+
Readonly<{
|
|
16
|
+
type: 'timing';
|
|
17
|
+
duration: number;
|
|
18
|
+
controlPoints: readonly [number, number, number, number];
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
export type SpringClipAnimation = SmoothClipAnimationBase &
|
|
22
|
+
Readonly<{
|
|
23
|
+
type: 'spring';
|
|
24
|
+
mass?: number;
|
|
25
|
+
stiffness?: number;
|
|
26
|
+
damping?: number;
|
|
27
|
+
/** Normalized progress velocity in inverse seconds. */
|
|
28
|
+
velocity?: number;
|
|
29
|
+
/** Relative mechanical energy used to determine settlement. */
|
|
30
|
+
energyThreshold?: number;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
export type SmoothClipAnimation = TimingClipAnimation | SpringClipAnimation;
|
|
34
|
+
|
|
35
|
+
declare const smoothClipRefBrand: unique symbol;
|
|
36
|
+
/** Opaque, worklet-safe identity for one mounted clip host. */
|
|
37
|
+
export type SmoothClipRef = Readonly<{
|
|
38
|
+
readonly [smoothClipRefBrand]: true;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
declare const smoothClipRunBrand: unique symbol;
|
|
42
|
+
/** Opaque identity for a native transaction started on the UI runtime. */
|
|
43
|
+
export type SmoothClipRunHandle = Readonly<{
|
|
44
|
+
readonly [smoothClipRunBrand]: true;
|
|
45
|
+
}>;
|
|
46
|
+
|
|
47
|
+
export type SmoothClipCompletion = Readonly<{
|
|
48
|
+
finished: boolean;
|
|
49
|
+
/** Present only when supplied by the UI-runtime caller. */
|
|
50
|
+
completionTag?: number;
|
|
51
|
+
}>;
|
|
52
|
+
|
|
53
|
+
export type SmoothClipReactRun = Readonly<{
|
|
54
|
+
finished: Promise<boolean>;
|
|
55
|
+
cancel(): void;
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
export type SmoothClipControllerOptions = Readonly<{
|
|
59
|
+
/** Stable React-runtime callback for UI- and React-runtime transactions. */
|
|
60
|
+
onAnimationComplete?: (result: SmoothClipCompletion) => void;
|
|
61
|
+
}>;
|
|
62
|
+
|
|
63
|
+
export type SmoothClipControllerUI = Readonly<{
|
|
64
|
+
beginInteraction(): CanonicalSmoothClipPresentation;
|
|
65
|
+
setFrame(frame: SmoothClipPresentation): void;
|
|
66
|
+
animateTo(
|
|
67
|
+
target: SmoothClipPresentation,
|
|
68
|
+
animation: SmoothClipAnimation,
|
|
69
|
+
completionTag?: number
|
|
70
|
+
): SmoothClipRunHandle | null;
|
|
71
|
+
cancel(run: SmoothClipRunHandle): CanonicalSmoothClipPresentation;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
export type SmoothClipControllerReact = Readonly<{
|
|
75
|
+
animateTo(
|
|
76
|
+
target: SmoothClipPresentation,
|
|
77
|
+
animation: SmoothClipAnimation
|
|
78
|
+
): SmoothClipReactRun;
|
|
79
|
+
}>;
|
|
80
|
+
|
|
81
|
+
export type SmoothClipController = Readonly<{
|
|
82
|
+
ref: SmoothClipRef;
|
|
83
|
+
ui: SmoothClipControllerUI;
|
|
84
|
+
react: SmoothClipControllerReact;
|
|
85
|
+
}>;
|
|
86
|
+
|
|
87
|
+
export type SmoothClipInitialFrame = ClipGeometry | SmoothClipPresentation;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSmoothClipController } from './controllers.native';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSmoothClipController } from './controllers.native';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { scheduleOnUI } from 'react-native-worklets';
|
|
3
|
+
import type {
|
|
4
|
+
SmoothClipController,
|
|
5
|
+
SmoothClipControllerOptions,
|
|
6
|
+
SmoothClipInitialFrame,
|
|
7
|
+
} from './controllerTypes';
|
|
8
|
+
import { createSmoothClipRef, setControllerRef } from './controllerInternals';
|
|
9
|
+
import { destroyController } from './controllerLifecycle';
|
|
10
|
+
import { allocateSmoothClipId } from './ids';
|
|
11
|
+
import {
|
|
12
|
+
canonicalizeClipPresentation,
|
|
13
|
+
createClipPresentation,
|
|
14
|
+
type CanonicalSmoothClipPresentation,
|
|
15
|
+
} from './geometry';
|
|
16
|
+
import { useSmoothClipGroup } from './groups.native';
|
|
17
|
+
import { presentationPacket } from './presentationCodec';
|
|
18
|
+
import NativeSmoothClipModule from './smoothClipNative';
|
|
19
|
+
|
|
20
|
+
const setClipPresentationHostFunction =
|
|
21
|
+
NativeSmoothClipModule.setClipPresentation;
|
|
22
|
+
|
|
23
|
+
export function useSmoothClipController(
|
|
24
|
+
initialValue: SmoothClipInitialFrame,
|
|
25
|
+
options: SmoothClipControllerOptions = {}
|
|
26
|
+
): SmoothClipController {
|
|
27
|
+
// A controller is intentionally a one-member transaction group. This keeps
|
|
28
|
+
// validation, native dispatch, cancellation, completion, and lifecycle races
|
|
29
|
+
// on the same path as multi-member transitions.
|
|
30
|
+
const group = useSmoothClipGroup(options);
|
|
31
|
+
const initialRequested =
|
|
32
|
+
'clip' in initialValue
|
|
33
|
+
? initialValue
|
|
34
|
+
: createClipPresentation(initialValue);
|
|
35
|
+
const canonicalInitial = canonicalizeClipPresentation(initialRequested);
|
|
36
|
+
if (canonicalInitial === null) {
|
|
37
|
+
throw new Error('[SmoothClipView] Initial presentation must be finite.');
|
|
38
|
+
}
|
|
39
|
+
const initialFrameRef = useRef<CanonicalSmoothClipPresentation | null>(null);
|
|
40
|
+
initialFrameRef.current ??= canonicalInitial;
|
|
41
|
+
const initialFrame = initialFrameRef.current;
|
|
42
|
+
|
|
43
|
+
const idRef = useRef(0);
|
|
44
|
+
const controllerRef = useRef<SmoothClipController | null>(null);
|
|
45
|
+
if (idRef.current === 0) idRef.current = allocateSmoothClipId();
|
|
46
|
+
const controllerId = idRef.current;
|
|
47
|
+
|
|
48
|
+
if (controllerRef.current === null) {
|
|
49
|
+
const ref = createSmoothClipRef(controllerId);
|
|
50
|
+
const controller: SmoothClipController = {
|
|
51
|
+
ref,
|
|
52
|
+
ui: {
|
|
53
|
+
beginInteraction() {
|
|
54
|
+
'worklet';
|
|
55
|
+
return group.ui.beginInteraction([ref])[0]?.frame ?? initialFrame;
|
|
56
|
+
},
|
|
57
|
+
setFrame(frame) {
|
|
58
|
+
'worklet';
|
|
59
|
+
group.ui.setFrames([{ clip: ref, frame }]);
|
|
60
|
+
},
|
|
61
|
+
animateTo(target, animation, completionTag = 0) {
|
|
62
|
+
'worklet';
|
|
63
|
+
return group.ui.animateTo(
|
|
64
|
+
[{ clip: ref, target }],
|
|
65
|
+
animation,
|
|
66
|
+
completionTag
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
cancel(run) {
|
|
70
|
+
'worklet';
|
|
71
|
+
return group.ui.cancel(run)[0]?.frame ?? initialFrame;
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
react: {
|
|
75
|
+
animateTo(target, animation) {
|
|
76
|
+
return group.react.animateTo([{ clip: ref, target }], animation);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
setControllerRef(controller, { ref, initialFrame });
|
|
81
|
+
controllerRef.current = controller;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
scheduleOnUI(
|
|
86
|
+
(id: number, frame: CanonicalSmoothClipPresentation) => {
|
|
87
|
+
'worklet';
|
|
88
|
+
setClipPresentationHostFunction(
|
|
89
|
+
id,
|
|
90
|
+
presentationPacket(frame),
|
|
91
|
+
true,
|
|
92
|
+
false
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
controllerId,
|
|
96
|
+
initialFrame
|
|
97
|
+
);
|
|
98
|
+
return () => {
|
|
99
|
+
destroyController(controllerId);
|
|
100
|
+
};
|
|
101
|
+
}, [controllerId, initialFrame]);
|
|
102
|
+
|
|
103
|
+
return controllerRef.current;
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSmoothClipController } from './controllers.native';
|
package/src/geometry.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
+
/* eslint-disable no-bitwise -- React Native colors are converted to RRGGBBAA */
|
|
2
|
+
import type { ColorValue } from 'react-native';
|
|
3
|
+
import { processColor } from 'react-native-reanimated';
|
|
4
|
+
|
|
1
5
|
export type ClipCurve = 'circular' | 'continuous';
|
|
2
6
|
|
|
7
|
+
/** One numeric outset shadow following React Native's `boxShadow` field names. */
|
|
8
|
+
export type ClipBoxShadow = Readonly<{
|
|
9
|
+
color?: ColorValue;
|
|
10
|
+
offsetX: number;
|
|
11
|
+
offsetY: number;
|
|
12
|
+
/** CSS box-shadow blur radius. iOS converts this to CALayer radius / 2. */
|
|
13
|
+
blurRadius?: number;
|
|
14
|
+
spreadDistance?: number;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export type CanonicalClipBoxShadow = Readonly<{
|
|
18
|
+
/** React Native numeric RRGGBBAA color; canonicalization is idempotent. */
|
|
19
|
+
color: ColorValue;
|
|
20
|
+
offsetX: number;
|
|
21
|
+
offsetY: number;
|
|
22
|
+
blurRadius: number;
|
|
23
|
+
spreadDistance: number;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
3
26
|
export type ClipGeometry = Readonly<{
|
|
4
27
|
x: number;
|
|
5
28
|
y: number;
|
|
@@ -19,7 +42,7 @@ export type CanonicalClipGeometry = Readonly<{
|
|
|
19
42
|
y: number;
|
|
20
43
|
width: number;
|
|
21
44
|
height: number;
|
|
22
|
-
/**
|
|
45
|
+
/** Canonical all-corners value; zero when the corner radii differ. */
|
|
23
46
|
radius: number;
|
|
24
47
|
topLeftRadius: number;
|
|
25
48
|
topRightRadius: number;
|
|
@@ -28,17 +51,13 @@ export type CanonicalClipGeometry = Readonly<{
|
|
|
28
51
|
curve: ClipCurve;
|
|
29
52
|
}>;
|
|
30
53
|
|
|
31
|
-
export type ClipBounds = Readonly<{
|
|
32
|
-
width: number;
|
|
33
|
-
height: number;
|
|
34
|
-
}>;
|
|
35
|
-
|
|
36
54
|
export type SmoothClipPresentation = Readonly<{
|
|
37
55
|
clip: ClipGeometry;
|
|
38
56
|
contentTranslateX: number;
|
|
39
57
|
contentTranslateY: number;
|
|
40
58
|
/** Defaults to 1 when omitted. */
|
|
41
59
|
contentScale?: number;
|
|
60
|
+
boxShadow?: ClipBoxShadow;
|
|
42
61
|
}>;
|
|
43
62
|
|
|
44
63
|
export type CanonicalSmoothClipPresentation = Readonly<{
|
|
@@ -46,8 +65,11 @@ export type CanonicalSmoothClipPresentation = Readonly<{
|
|
|
46
65
|
contentTranslateX: number;
|
|
47
66
|
contentTranslateY: number;
|
|
48
67
|
contentScale: number;
|
|
68
|
+
boxShadow?: CanonicalClipBoxShadow;
|
|
49
69
|
}>;
|
|
50
70
|
|
|
71
|
+
const DEFAULT_SHADOW_COLOR = '#000000ff';
|
|
72
|
+
|
|
51
73
|
function isClipCurve(curve: ClipCurve | undefined): boolean {
|
|
52
74
|
'worklet';
|
|
53
75
|
return curve === undefined || curve === 'circular' || curve === 'continuous';
|
|
@@ -58,14 +80,74 @@ function isFiniteOptionalNumber(value: number | undefined): boolean {
|
|
|
58
80
|
return value === undefined || Number.isFinite(value);
|
|
59
81
|
}
|
|
60
82
|
|
|
83
|
+
function canonicalizeClipBoxShadow(
|
|
84
|
+
shadow: ClipBoxShadow | undefined
|
|
85
|
+
): CanonicalClipBoxShadow | null | undefined {
|
|
86
|
+
'worklet';
|
|
87
|
+
if (shadow === undefined) return undefined;
|
|
88
|
+
const { offsetX, offsetY } = shadow;
|
|
89
|
+
const blurRadius = shadow.blurRadius ?? 0;
|
|
90
|
+
const spreadDistance = shadow.spreadDistance ?? 0;
|
|
91
|
+
if (
|
|
92
|
+
!Number.isFinite(offsetX) ||
|
|
93
|
+
!Number.isFinite(offsetY) ||
|
|
94
|
+
!Number.isFinite(blurRadius) ||
|
|
95
|
+
!Number.isFinite(spreadDistance)
|
|
96
|
+
) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const processedColor =
|
|
100
|
+
shadow.color === undefined
|
|
101
|
+
? processColor(DEFAULT_SHADOW_COLOR)
|
|
102
|
+
: processColor(shadow.color);
|
|
103
|
+
// Platform and dynamic colors cannot be represented by the scalar native
|
|
104
|
+
// animation protocol. Callers can resolve those colors before presenting.
|
|
105
|
+
if (typeof processedColor !== 'number') return null;
|
|
106
|
+
const unsignedProcessedColor = processedColor >>> 0;
|
|
107
|
+
const color =
|
|
108
|
+
((unsignedProcessedColor << 8) | (unsignedProcessedColor >>> 24)) >>> 0;
|
|
109
|
+
return {
|
|
110
|
+
color: color as unknown as ColorValue,
|
|
111
|
+
offsetX,
|
|
112
|
+
offsetY,
|
|
113
|
+
blurRadius: Math.max(0, blurRadius),
|
|
114
|
+
spreadDistance,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function clipBoxShadowEquals(
|
|
119
|
+
first: ClipBoxShadow | undefined,
|
|
120
|
+
second: ClipBoxShadow | undefined
|
|
121
|
+
): boolean {
|
|
122
|
+
'worklet';
|
|
123
|
+
if (first === undefined || second === undefined) return first === second;
|
|
124
|
+
const canonicalFirst = canonicalizeClipBoxShadow(first);
|
|
125
|
+
const canonicalSecond = canonicalizeClipBoxShadow(second);
|
|
126
|
+
return (
|
|
127
|
+
canonicalFirst !== null &&
|
|
128
|
+
canonicalFirst !== undefined &&
|
|
129
|
+
canonicalSecond !== null &&
|
|
130
|
+
canonicalSecond !== undefined &&
|
|
131
|
+
canonicalFirst.color === canonicalSecond.color &&
|
|
132
|
+
canonicalFirst.offsetX === canonicalSecond.offsetX &&
|
|
133
|
+
canonicalFirst.offsetY === canonicalSecond.offsetY &&
|
|
134
|
+
canonicalFirst.blurRadius === canonicalSecond.blurRadius &&
|
|
135
|
+
canonicalFirst.spreadDistance === canonicalSecond.spreadDistance
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
61
139
|
export function isFiniteClipGeometry(geometry: ClipGeometry): boolean {
|
|
62
140
|
'worklet';
|
|
141
|
+
const width = Math.max(0, geometry.width);
|
|
142
|
+
const height = Math.max(0, geometry.height);
|
|
63
143
|
return (
|
|
64
144
|
Number.isFinite(geometry.x) &&
|
|
65
145
|
Number.isFinite(geometry.y) &&
|
|
66
146
|
Number.isFinite(geometry.width) &&
|
|
67
147
|
Number.isFinite(geometry.height) &&
|
|
68
148
|
Number.isFinite(geometry.radius) &&
|
|
149
|
+
Number.isFinite(geometry.x + width) &&
|
|
150
|
+
Number.isFinite(geometry.y + height) &&
|
|
69
151
|
isFiniteOptionalNumber(geometry.topLeftRadius) &&
|
|
70
152
|
isFiniteOptionalNumber(geometry.topRightRadius) &&
|
|
71
153
|
isFiniteOptionalNumber(geometry.bottomRightRadius) &&
|
|
@@ -129,12 +211,14 @@ export function isFiniteClipPresentation(
|
|
|
129
211
|
): boolean {
|
|
130
212
|
'worklet';
|
|
131
213
|
const contentScale = resolvedContentScale(presentation);
|
|
214
|
+
const shadow = canonicalizeClipBoxShadow(presentation.boxShadow);
|
|
132
215
|
return (
|
|
133
216
|
isFiniteClipGeometry(presentation.clip) &&
|
|
134
217
|
Number.isFinite(presentation.contentTranslateX) &&
|
|
135
218
|
Number.isFinite(presentation.contentTranslateY) &&
|
|
136
219
|
Number.isFinite(contentScale) &&
|
|
137
|
-
contentScale > 0
|
|
220
|
+
contentScale > 0 &&
|
|
221
|
+
shadow !== null
|
|
138
222
|
);
|
|
139
223
|
}
|
|
140
224
|
|
|
@@ -148,7 +232,8 @@ export function clipPresentationEquals(
|
|
|
148
232
|
clipGeometryEquals(first.clip, second.clip) &&
|
|
149
233
|
first.contentTranslateX === second.contentTranslateX &&
|
|
150
234
|
first.contentTranslateY === second.contentTranslateY &&
|
|
151
|
-
resolvedContentScale(first) === resolvedContentScale(second)
|
|
235
|
+
resolvedContentScale(first) === resolvedContentScale(second) &&
|
|
236
|
+
clipBoxShadowEquals(first.boxShadow, second.boxShadow)
|
|
152
237
|
);
|
|
153
238
|
}
|
|
154
239
|
|
|
@@ -179,15 +264,15 @@ function createCanonicalClipGeometry(
|
|
|
179
264
|
if (left > 0) scale = Math.min(scale, height / left);
|
|
180
265
|
scale = Math.max(0, Math.min(1, scale));
|
|
181
266
|
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const
|
|
267
|
+
const canonicalTopLeftRadius = topLeftRadius * scale;
|
|
268
|
+
const canonicalTopRightRadius = topRightRadius * scale;
|
|
269
|
+
const canonicalBottomRightRadius = bottomRightRadius * scale;
|
|
270
|
+
const canonicalBottomLeftRadius = bottomLeftRadius * scale;
|
|
186
271
|
const radius =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
?
|
|
272
|
+
canonicalTopLeftRadius === canonicalTopRightRadius &&
|
|
273
|
+
canonicalTopRightRadius === canonicalBottomRightRadius &&
|
|
274
|
+
canonicalBottomRightRadius === canonicalBottomLeftRadius
|
|
275
|
+
? canonicalTopLeftRadius
|
|
191
276
|
: 0;
|
|
192
277
|
|
|
193
278
|
return {
|
|
@@ -196,10 +281,10 @@ function createCanonicalClipGeometry(
|
|
|
196
281
|
width,
|
|
197
282
|
height,
|
|
198
283
|
radius,
|
|
199
|
-
topLeftRadius:
|
|
200
|
-
topRightRadius:
|
|
201
|
-
bottomRightRadius:
|
|
202
|
-
bottomLeftRadius:
|
|
284
|
+
topLeftRadius: canonicalTopLeftRadius,
|
|
285
|
+
topRightRadius: canonicalTopRightRadius,
|
|
286
|
+
bottomRightRadius: canonicalBottomRightRadius,
|
|
287
|
+
bottomLeftRadius: canonicalBottomLeftRadius,
|
|
203
288
|
curve: resolvedCurve(geometry),
|
|
204
289
|
};
|
|
205
290
|
}
|
|
@@ -232,12 +317,15 @@ export function canonicalizeClipPresentation(
|
|
|
232
317
|
|
|
233
318
|
const clip = canonicalizeClipGeometry(presentation.clip);
|
|
234
319
|
if (clip === null) return null;
|
|
320
|
+
const boxShadow = canonicalizeClipBoxShadow(presentation.boxShadow);
|
|
321
|
+
if (boxShadow === null) return null;
|
|
235
322
|
|
|
236
323
|
return {
|
|
237
324
|
clip,
|
|
238
325
|
contentTranslateX: presentation.contentTranslateX,
|
|
239
326
|
contentTranslateY: presentation.contentTranslateY,
|
|
240
327
|
contentScale: resolvedContentScale(presentation),
|
|
328
|
+
...(boxShadow === undefined ? {} : { boxShadow }),
|
|
241
329
|
};
|
|
242
330
|
}
|
|
243
331
|
|
|
@@ -247,10 +335,12 @@ export function createClipPresentation(
|
|
|
247
335
|
clip: ClipGeometry,
|
|
248
336
|
contentTranslateX = 0,
|
|
249
337
|
contentTranslateY = 0,
|
|
250
|
-
contentScale = 1
|
|
338
|
+
contentScale = 1,
|
|
339
|
+
boxShadow?: ClipBoxShadow
|
|
251
340
|
): CanonicalSmoothClipPresentation {
|
|
252
341
|
'worklet';
|
|
253
342
|
const canonicalClip = canonicalizeClipGeometry(clip);
|
|
343
|
+
const canonicalShadow = canonicalizeClipBoxShadow(boxShadow);
|
|
254
344
|
return {
|
|
255
345
|
// Keep invalid input observably invalid so downstream validation remains
|
|
256
346
|
// atomic instead of accidentally sanitizing a required scalar.
|
|
@@ -269,58 +359,12 @@ export function createClipPresentation(
|
|
|
269
359
|
contentTranslateX,
|
|
270
360
|
contentTranslateY,
|
|
271
361
|
contentScale,
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
export function normalizeClipGeometry(
|
|
280
|
-
geometry: ClipGeometry,
|
|
281
|
-
bounds: ClipBounds
|
|
282
|
-
): CanonicalClipGeometry | null {
|
|
283
|
-
'worklet';
|
|
284
|
-
if (
|
|
285
|
-
!isFiniteClipGeometry(geometry) ||
|
|
286
|
-
!Number.isFinite(bounds.width) ||
|
|
287
|
-
!Number.isFinite(bounds.height)
|
|
288
|
-
) {
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const boundsWidth = Math.max(0, bounds.width);
|
|
293
|
-
const boundsHeight = Math.max(0, bounds.height);
|
|
294
|
-
const requestedWidth = Math.max(0, geometry.width);
|
|
295
|
-
const requestedHeight = Math.max(0, geometry.height);
|
|
296
|
-
|
|
297
|
-
const left = Math.min(boundsWidth, Math.max(0, geometry.x));
|
|
298
|
-
const top = Math.min(boundsHeight, Math.max(0, geometry.y));
|
|
299
|
-
const right = Math.min(boundsWidth, Math.max(0, geometry.x + requestedWidth));
|
|
300
|
-
const bottom = Math.min(
|
|
301
|
-
boundsHeight,
|
|
302
|
-
Math.max(0, geometry.y + requestedHeight)
|
|
303
|
-
);
|
|
304
|
-
const width = Math.max(0, right - left);
|
|
305
|
-
const height = Math.max(0, bottom - top);
|
|
306
|
-
|
|
307
|
-
return createCanonicalClipGeometry(geometry, left, top, width, height);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
export function normalizeClipPresentation(
|
|
311
|
-
presentation: SmoothClipPresentation,
|
|
312
|
-
bounds: ClipBounds
|
|
313
|
-
): CanonicalSmoothClipPresentation | null {
|
|
314
|
-
'worklet';
|
|
315
|
-
if (!isFiniteClipPresentation(presentation)) return null;
|
|
316
|
-
|
|
317
|
-
const clip = normalizeClipGeometry(presentation.clip, bounds);
|
|
318
|
-
if (clip === null) return null;
|
|
319
|
-
|
|
320
|
-
return {
|
|
321
|
-
clip,
|
|
322
|
-
contentTranslateX: presentation.contentTranslateX,
|
|
323
|
-
contentTranslateY: presentation.contentTranslateY,
|
|
324
|
-
contentScale: resolvedContentScale(presentation),
|
|
362
|
+
...(canonicalShadow === undefined
|
|
363
|
+
? {}
|
|
364
|
+
: {
|
|
365
|
+
// Match the geometry constructor's invalid-input behavior: retain the
|
|
366
|
+
// bad source value so the driver's atomic validation rejects it.
|
|
367
|
+
boxShadow: (canonicalShadow ?? boxShadow) as CanonicalClipBoxShadow,
|
|
368
|
+
}),
|
|
325
369
|
};
|
|
326
370
|
}
|