react-native-smooth-clip-view 0.3.0 → 0.4.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 (199) hide show
  1. package/README.md +135 -386
  2. package/android/build.gradle +4 -7
  3. package/android/proguard-rules.pro +2 -2
  4. package/android/src/main/cpp/SmoothClipAndroid.h +9 -12
  5. package/android/src/main/cpp/SmoothClipBindings.cpp +300 -704
  6. package/android/src/main/cpp/SmoothClipRegistry.cpp +348 -594
  7. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +36 -227
  8. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +16 -19
  9. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +36 -294
  10. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +369 -486
  11. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +75 -77
  12. package/cpp/SmoothClipAnimationCurve.h +237 -346
  13. package/cpp/SmoothClipRegistry.h +24 -52
  14. package/cpp/SmoothClipSharedGeometry.h +64 -94
  15. package/cpp/SmoothClipVelocityTracker.h +5 -5
  16. package/ios/SmoothClipGeometry.h +23 -34
  17. package/ios/SmoothClipRegistry.mm +206 -711
  18. package/ios/SmoothClipTurboModule.cpp +217 -715
  19. package/ios/SmoothClipTurboModule.h +54 -297
  20. package/ios/SmoothClipView.mm +599 -505
  21. package/ios/SmoothClipViewRegistry.h +2 -7
  22. package/lib/module/NativeSmoothClipModule.js.map +1 -1
  23. package/lib/module/SmoothClipView.ios.js +84 -17
  24. package/lib/module/SmoothClipView.ios.js.map +1 -1
  25. package/lib/module/SmoothClipView.js +1 -1
  26. package/lib/module/SmoothClipView.js.map +1 -1
  27. package/lib/module/SmoothClipViewNativeComponent.ts +20 -26
  28. package/lib/module/capabilities.native.js +1 -19
  29. package/lib/module/capabilities.native.js.map +1 -1
  30. package/lib/module/capabilityTypes.js +0 -9
  31. package/lib/module/capabilityTypes.js.map +1 -1
  32. package/lib/module/controllerInternals.js +28 -0
  33. package/lib/module/controllerInternals.js.map +1 -0
  34. package/lib/module/controllerLifecycle.js +9 -0
  35. package/lib/module/controllerLifecycle.js.map +1 -0
  36. package/lib/module/controllerTypes.js +4 -0
  37. package/lib/module/controllerTypes.js.map +1 -0
  38. package/lib/module/controllers.android.js +4 -0
  39. package/lib/module/controllers.android.js.map +1 -0
  40. package/lib/module/controllers.ios.js +4 -0
  41. package/lib/module/controllers.ios.js.map +1 -0
  42. package/lib/module/controllers.js +4 -0
  43. package/lib/module/controllers.js.map +1 -0
  44. package/lib/module/controllers.native.js +89 -0
  45. package/lib/module/controllers.native.js.map +1 -0
  46. package/lib/module/geometry.js +71 -51
  47. package/lib/module/geometry.js.map +1 -1
  48. package/lib/module/groupTypes.js +4 -0
  49. package/lib/module/{groupDriverTypes.js.map → groupTypes.js.map} +1 -1
  50. package/lib/module/groups.android.js +4 -0
  51. package/lib/module/groups.android.js.map +1 -0
  52. package/lib/module/groups.ios.js +4 -0
  53. package/lib/module/groups.ios.js.map +1 -0
  54. package/lib/module/groups.js +4 -0
  55. package/lib/module/groups.js.map +1 -0
  56. package/lib/module/groups.native.js +399 -0
  57. package/lib/module/groups.native.js.map +1 -0
  58. package/lib/module/ids.js +8 -0
  59. package/lib/module/ids.js.map +1 -0
  60. package/lib/module/index.js +3 -3
  61. package/lib/module/index.js.map +1 -1
  62. package/lib/module/nativeCompletion.js +37 -16
  63. package/lib/module/nativeCompletion.js.map +1 -1
  64. package/lib/module/presentationCodec.js +81 -0
  65. package/lib/module/presentationCodec.js.map +1 -0
  66. package/lib/typescript/src/NativeSmoothClipModule.d.ts +14 -23
  67. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  68. package/lib/typescript/src/SmoothClipView.d.ts +1 -1
  69. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  70. package/lib/typescript/src/SmoothClipView.ios.d.ts +18 -14
  71. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  72. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +10 -5
  73. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  74. package/lib/typescript/src/capabilities.native.d.ts +1 -1
  75. package/lib/typescript/src/capabilities.native.d.ts.map +1 -1
  76. package/lib/typescript/src/capabilityTypes.d.ts +0 -6
  77. package/lib/typescript/src/capabilityTypes.d.ts.map +1 -1
  78. package/lib/typescript/src/controllerInternals.d.ts +14 -0
  79. package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
  80. package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
  81. package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
  82. package/lib/typescript/src/controllerTypes.d.ts +62 -0
  83. package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
  84. package/lib/typescript/src/controllers.android.d.ts +2 -0
  85. package/lib/typescript/src/controllers.android.d.ts.map +1 -0
  86. package/lib/typescript/src/controllers.d.ts +2 -0
  87. package/lib/typescript/src/controllers.d.ts.map +1 -0
  88. package/lib/typescript/src/controllers.ios.d.ts +2 -0
  89. package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
  90. package/lib/typescript/src/controllers.native.d.ts +3 -0
  91. package/lib/typescript/src/controllers.native.d.ts.map +1 -0
  92. package/lib/typescript/src/geometry.d.ts +22 -12
  93. package/lib/typescript/src/geometry.d.ts.map +1 -1
  94. package/lib/typescript/src/groupTypes.d.ts +31 -0
  95. package/lib/typescript/src/groupTypes.d.ts.map +1 -0
  96. package/lib/typescript/src/groups.android.d.ts +2 -0
  97. package/lib/typescript/src/groups.android.d.ts.map +1 -0
  98. package/lib/typescript/src/groups.d.ts +2 -0
  99. package/lib/typescript/src/groups.d.ts.map +1 -0
  100. package/lib/typescript/src/groups.ios.d.ts +2 -0
  101. package/lib/typescript/src/groups.ios.d.ts.map +1 -0
  102. package/lib/typescript/src/groups.native.d.ts +3 -0
  103. package/lib/typescript/src/groups.native.d.ts.map +1 -0
  104. package/lib/typescript/src/ids.d.ts +2 -0
  105. package/lib/typescript/src/ids.d.ts.map +1 -0
  106. package/lib/typescript/src/index.d.ts +6 -6
  107. package/lib/typescript/src/index.d.ts.map +1 -1
  108. package/lib/typescript/src/nativeCompletion.d.ts +16 -6
  109. package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
  110. package/lib/typescript/src/presentationCodec.d.ts +10 -0
  111. package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
  112. package/package.json +8 -3
  113. package/src/NativeSmoothClipModule.ts +41 -292
  114. package/src/SmoothClipView.ios.tsx +130 -23
  115. package/src/SmoothClipView.tsx +5 -1
  116. package/src/SmoothClipViewNativeComponent.ts +20 -26
  117. package/src/capabilities.native.ts +2 -52
  118. package/src/capabilityTypes.ts +0 -14
  119. package/src/controllerInternals.ts +52 -0
  120. package/src/controllerLifecycle.ts +6 -0
  121. package/src/controllerTypes.ts +87 -0
  122. package/src/controllers.android.ts +1 -0
  123. package/src/controllers.ios.ts +1 -0
  124. package/src/controllers.native.ts +104 -0
  125. package/src/controllers.ts +1 -0
  126. package/src/geometry.ts +118 -74
  127. package/src/groupTypes.ts +53 -0
  128. package/src/groups.android.ts +1 -0
  129. package/src/groups.ios.ts +1 -0
  130. package/src/groups.native.ts +604 -0
  131. package/src/groups.ts +1 -0
  132. package/src/ids.ts +6 -0
  133. package/src/index.ts +20 -28
  134. package/src/nativeCompletion.ts +67 -32
  135. package/src/presentationCodec.ts +134 -0
  136. package/lib/module/driverState.js +0 -82
  137. package/lib/module/driverState.js.map +0 -1
  138. package/lib/module/driverTypes.js +0 -4
  139. package/lib/module/driverTypes.js.map +0 -1
  140. package/lib/module/drivers.android.js +0 -6
  141. package/lib/module/drivers.android.js.map +0 -1
  142. package/lib/module/drivers.ios.js +0 -6
  143. package/lib/module/drivers.ios.js.map +0 -1
  144. package/lib/module/drivers.js +0 -4
  145. package/lib/module/drivers.js.map +0 -1
  146. package/lib/module/drivers.native.js +0 -756
  147. package/lib/module/drivers.native.js.map +0 -1
  148. package/lib/module/groupDriverTypes.js +0 -4
  149. package/lib/module/groupDrivers.android.js +0 -4
  150. package/lib/module/groupDrivers.android.js.map +0 -1
  151. package/lib/module/groupDrivers.ios.js +0 -4
  152. package/lib/module/groupDrivers.ios.js.map +0 -1
  153. package/lib/module/groupDrivers.js +0 -4
  154. package/lib/module/groupDrivers.js.map +0 -1
  155. package/lib/module/groupDrivers.native.js +0 -799
  156. package/lib/module/groupDrivers.native.js.map +0 -1
  157. package/lib/module/presentationProtocol.js +0 -15
  158. package/lib/module/presentationProtocol.js.map +0 -1
  159. package/lib/module/reactRequests.js +0 -56
  160. package/lib/module/reactRequests.js.map +0 -1
  161. package/lib/typescript/src/driverState.d.ts +0 -26
  162. package/lib/typescript/src/driverState.d.ts.map +0 -1
  163. package/lib/typescript/src/driverTypes.d.ts +0 -121
  164. package/lib/typescript/src/driverTypes.d.ts.map +0 -1
  165. package/lib/typescript/src/drivers.android.d.ts +0 -3
  166. package/lib/typescript/src/drivers.android.d.ts.map +0 -1
  167. package/lib/typescript/src/drivers.d.ts +0 -3
  168. package/lib/typescript/src/drivers.d.ts.map +0 -1
  169. package/lib/typescript/src/drivers.ios.d.ts +0 -3
  170. package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
  171. package/lib/typescript/src/drivers.native.d.ts +0 -5
  172. package/lib/typescript/src/drivers.native.d.ts.map +0 -1
  173. package/lib/typescript/src/groupDriverTypes.d.ts +0 -66
  174. package/lib/typescript/src/groupDriverTypes.d.ts.map +0 -1
  175. package/lib/typescript/src/groupDrivers.android.d.ts +0 -2
  176. package/lib/typescript/src/groupDrivers.android.d.ts.map +0 -1
  177. package/lib/typescript/src/groupDrivers.d.ts +0 -2
  178. package/lib/typescript/src/groupDrivers.d.ts.map +0 -1
  179. package/lib/typescript/src/groupDrivers.ios.d.ts +0 -2
  180. package/lib/typescript/src/groupDrivers.ios.d.ts.map +0 -1
  181. package/lib/typescript/src/groupDrivers.native.d.ts +0 -4
  182. package/lib/typescript/src/groupDrivers.native.d.ts.map +0 -1
  183. package/lib/typescript/src/presentationProtocol.d.ts +0 -4
  184. package/lib/typescript/src/presentationProtocol.d.ts.map +0 -1
  185. package/lib/typescript/src/reactRequests.d.ts +0 -9
  186. package/lib/typescript/src/reactRequests.d.ts.map +0 -1
  187. package/src/driverState.ts +0 -118
  188. package/src/driverTypes.ts +0 -171
  189. package/src/drivers.android.ts +0 -8
  190. package/src/drivers.ios.ts +0 -8
  191. package/src/drivers.native.ts +0 -1599
  192. package/src/drivers.ts +0 -6
  193. package/src/groupDriverTypes.ts +0 -117
  194. package/src/groupDrivers.android.ts +0 -1
  195. package/src/groupDrivers.ios.ts +0 -1
  196. package/src/groupDrivers.native.ts +0 -1189
  197. package/src/groupDrivers.ts +0 -1
  198. package/src/presentationProtocol.ts +0 -28
  199. package/src/reactRequests.ts +0 -76
@@ -1,799 +0,0 @@
1
- "use strict";
2
-
3
- import { useEffect, useRef } from 'react';
4
- import { Platform } from 'react-native';
5
- import { useSharedValue } from 'react-native-reanimated';
6
- import { isRNRuntime, scheduleOnRN, scheduleOnUI } from 'react-native-worklets';
7
- import NativeSmoothClipModule from './smoothClipNative';
8
- import { getSmoothClipCapabilities } from './capabilities';
9
- import { allocateDriverId } from "./driverState.js";
10
- import { canonicalizeClipPresentation, clipPresentationEquals } from "./geometry.js";
11
- const INTERACTIVE = 0;
12
- const NATIVE = 1;
13
- const PRESENTATION_STRIDE = 12;
14
- const SNAPSHOT_STRIDE = 13;
15
- const MOTION_ENTRY_STRIDE = 26;
16
- const NEEDS_START_STAMP = Platform.OS === 'android';
17
- const GROUPS_SUPPORTED = getSmoothClipCapabilities().groups;
18
- const nativeGroups = NativeSmoothClipModule;
19
- const beginGroupInteractionV2HostFunction = nativeGroups.beginGroupInteractionV2;
20
- const snapshotGroupV2HostFunction = nativeGroups.snapshotGroupV2;
21
- const setClipPresentationBatchV2HostFunction = nativeGroups.setClipPresentationBatchV2;
22
- const animateTimingGroupV2HostFunction = nativeGroups.animateTimingGroupV2;
23
- const animateSpringGroupV2HostFunction = nativeGroups.animateSpringGroupV2;
24
- const animateKeyframesGroupV2HostFunction = nativeGroups.animateKeyframesGroupV2;
25
- const cancelAnimationGroupV2HostFunction = nativeGroups.cancelAnimationGroupV2;
26
- const nativeGroupDriverStates = new Map();
27
- function deliverNativeGroupCompletion(controllerId, groupId, finished) {
28
- const state = nativeGroupDriverStates.get(controllerId);
29
- if (!state) return;
30
- const result = {
31
- groupId,
32
- finished
33
- };
34
- if (state.completionDeferrals > 0) {
35
- state.deferredCompletions.push(result);
36
- return;
37
- }
38
- state.callbackRef.current?.(result);
39
- }
40
- function deferNativeGroupCompletions(controllerId) {
41
- const state = nativeGroupDriverStates.get(controllerId);
42
- if (state) state.completionDeferrals += 1;
43
- }
44
- function releaseNativeGroupCompletions(controllerId) {
45
- const state = nativeGroupDriverStates.get(controllerId);
46
- if (!state || state.completionDeferrals === 0) return;
47
- state.completionDeferrals -= 1;
48
- if (state.completionDeferrals !== 0) return;
49
- const deferred = state.deferredCompletions.splice(0);
50
- for (const result of deferred) state.callbackRef.current?.(result);
51
- }
52
- function runtimeGroupRecords() {
53
- 'worklet';
54
-
55
- const runtime = globalThis;
56
- runtime.__smoothClipGroupRecords ??= {};
57
- return runtime.__smoothClipGroupRecords;
58
- }
59
- function fail(message) {
60
- 'worklet';
61
-
62
- throw new Error(`[SmoothClipView] ${message}`);
63
- }
64
- function uiOnly() {
65
- 'worklet';
66
-
67
- return fail('group.ui methods must run on the UI runtime. Use group.react from React code.');
68
- }
69
- function curveCode(presentation) {
70
- 'worklet';
71
-
72
- return presentation.clip.curve === 'continuous' ? 1 : 0;
73
- }
74
- function appendPresentation(values, presentation) {
75
- 'worklet';
76
-
77
- const {
78
- clip,
79
- contentTranslateX,
80
- contentTranslateY,
81
- contentScale
82
- } = presentation;
83
- values.push(clip.x, clip.y, clip.width, clip.height, clip.topLeftRadius, clip.topRightRadius, clip.bottomRightRadius, clip.bottomLeftRadius, curveCode(presentation), contentTranslateX, contentTranslateY, contentScale);
84
- }
85
- function presentationFromValues(values, offset) {
86
- 'worklet';
87
-
88
- if (values.length < offset + PRESENTATION_STRIDE) return null;
89
- return canonicalizeClipPresentation({
90
- clip: {
91
- x: values[offset],
92
- y: values[offset + 1],
93
- width: values[offset + 2],
94
- height: values[offset + 3],
95
- radius: 0,
96
- topLeftRadius: values[offset + 4],
97
- topRightRadius: values[offset + 5],
98
- bottomRightRadius: values[offset + 6],
99
- bottomLeftRadius: values[offset + 7],
100
- curve: values[offset + 8] === 1 ? 'continuous' : 'circular'
101
- },
102
- contentTranslateX: values[offset + 9],
103
- contentTranslateY: values[offset + 10],
104
- contentScale: values[offset + 11]
105
- });
106
- }
107
- function validateDrivers(drivers) {
108
- 'worklet';
109
-
110
- if (drivers.length === 0) return fail('A group must contain a driver.');
111
- const seen = {};
112
- const result = [];
113
- for (const driver of drivers) {
114
- const handle = driver.__smoothClipHandle;
115
- if (driver.kind !== 'hybrid' || handle === undefined || !Number.isSafeInteger(handle.driverId) || handle.driverId <= 0 || handle.disposed.value !== 0) {
116
- return fail('Every group participant must be a live SmoothClip driver.');
117
- }
118
- const key = String(handle.driverId);
119
- if (seen[key]) return fail('A group cannot contain duplicate drivers.');
120
- seen[key] = true;
121
- result.push({
122
- driver,
123
- handle
124
- });
125
- }
126
- return result;
127
- }
128
- function requireNativeGroups() {
129
- 'worklet';
130
-
131
- if (!GROUPS_SUPPORTED || beginGroupInteractionV2HostFunction === undefined || snapshotGroupV2HostFunction === undefined || setClipPresentationBatchV2HostFunction === undefined || animateTimingGroupV2HostFunction === undefined || animateSpringGroupV2HostFunction === undefined || animateKeyframesGroupV2HostFunction === undefined || cancelAnimationGroupV2HostFunction === undefined) {
132
- return fail('Native grouped presentation protocol V2 is unavailable.');
133
- }
134
- return {
135
- beginGroupInteractionV2: beginGroupInteractionV2HostFunction,
136
- snapshotGroupV2: snapshotGroupV2HostFunction,
137
- setClipPresentationBatchV2: setClipPresentationBatchV2HostFunction,
138
- animateTimingGroupV2: animateTimingGroupV2HostFunction,
139
- animateSpringGroupV2: animateSpringGroupV2HostFunction,
140
- animateKeyframesGroupV2: animateKeyframesGroupV2HostFunction,
141
- cancelAnimationGroupV2: cancelAnimationGroupV2HostFunction
142
- };
143
- }
144
- function parseSnapshots(drivers, values) {
145
- 'worklet';
146
-
147
- if (values.length !== drivers.length * SNAPSHOT_STRIDE) {
148
- return fail('Native returned an invalid group snapshot.');
149
- }
150
- const snapshots = [];
151
- let unavailable = false;
152
- for (let index = 0; index < drivers.length; index += 1) {
153
- const offset = index * SNAPSHOT_STRIDE;
154
- const readyCode = values[offset];
155
- let presentation = presentationFromValues(values, offset + 1);
156
- if (readyCode !== 0 && readyCode !== 1) {
157
- return fail('Native returned an invalid group snapshot.');
158
- }
159
- if (presentation === null && readyCode === 0) {
160
- presentation = canonicalizeClipPresentation(drivers[index]?.__smoothClipHandle?.presentation.value);
161
- unavailable = true;
162
- }
163
- if (presentation === null) {
164
- return fail('Native returned an invalid group snapshot.');
165
- }
166
- snapshots.push({
167
- driver: drivers[index],
168
- presentation,
169
- ready: readyCode === 1
170
- });
171
- }
172
- return {
173
- snapshots,
174
- unavailable
175
- };
176
- }
177
- function applyInteractiveSnapshots(snapshots) {
178
- 'worklet';
179
-
180
- // Keep individual driver listeners from re-emitting a transaction that the
181
- // group engine has already committed atomically.
182
- for (const snapshot of snapshots) {
183
- const handle = snapshot.driver.__smoothClipHandle;
184
- if (handle) handle.ownership.value = NATIVE;
185
- }
186
- for (const snapshot of snapshots) {
187
- const handle = snapshot.driver.__smoothClipHandle;
188
- if (!handle) continue;
189
- handle.activeAnimationId.value = 0;
190
- handle.presentation.value = snapshot.presentation;
191
- handle.ownership.value = INTERACTIVE;
192
- }
193
- }
194
- function suspensionPolicyCode(value) {
195
- 'worklet';
196
-
197
- return value === 'finish' ? 1 : 0;
198
- }
199
- function reduceMotionCode(value) {
200
- 'worklet';
201
-
202
- if (value === 'always') return 1;
203
- if (value === 'never') return 2;
204
- return 0;
205
- }
206
- function animationStartTimestamp() {
207
- 'worklet';
208
-
209
- if (!NEEDS_START_STAMP) return Number.NaN;
210
- const runtime = globalThis;
211
- return runtime.__frameTimestamp || (typeof runtime._getAnimationTimestamp === 'function' ? runtime._getAnimationTimestamp() : Number.NaN);
212
- }
213
- function animationIsFinite(animation) {
214
- 'worklet';
215
-
216
- if (animation.suspensionPolicy !== undefined && animation.suspensionPolicy !== 'pause' && animation.suspensionPolicy !== 'finish') {
217
- return false;
218
- }
219
- if (animation.type === 'timing') {
220
- if (!Array.isArray(animation.controlPoints) || animation.controlPoints.length !== 4) {
221
- return false;
222
- }
223
- const [x1, y1, x2, y2] = animation.controlPoints;
224
- return Number.isFinite(animation.duration) && animation.duration >= 0 && [x1, y1, x2, y2].every(Number.isFinite) && x1 >= 0 && x1 <= 1 && x2 >= 0 && x2 <= 1;
225
- }
226
- if (animation.type === 'spring') {
227
- const initialVelocity = animation.initialVelocity === 'inherit' || animation.initialVelocity === undefined ? 0 : animation.initialVelocity;
228
- return [animation.mass ?? 1, animation.stiffness ?? 100, animation.damping ?? 10, initialVelocity].every(Number.isFinite) && (animation.mass ?? 1) > 0 && (animation.stiffness ?? 100) > 0 && (animation.damping ?? 10) >= 0;
229
- }
230
- return animation.type === 'keyframes' && Number.isFinite(animation.duration) && animation.duration >= 0;
231
- }
232
- function springScaleIsProvablyPositive(starts, targets, animation) {
233
- 'worklet';
234
-
235
- let changesScale = false;
236
- for (let index = 0; index < starts.length; index += 1) {
237
- if (starts[index]?.contentScale !== targets[index]?.contentScale) {
238
- changesScale = true;
239
- break;
240
- }
241
- }
242
- if (!changesScale) return true;
243
-
244
- // A zero-velocity critically/over-damped unit-step response is monotonic,
245
- // so interpolation stays between two already-positive endpoints. Other
246
- // configurations may overshoot; callers can compile those to keyframes.
247
- const mass = animation.mass ?? 1;
248
- const stiffness = animation.stiffness ?? 100;
249
- const damping = animation.damping ?? 10;
250
- return animation.initialVelocity === 0 && damping * damping >= 4 * mass * stiffness;
251
- }
252
- function snapshotsForMotionStarts(validDrivers, entries) {
253
- 'worklet';
254
-
255
- const missingFrom = entries.some(entry => entry.from === undefined);
256
- const sampled = missingFrom ? parseSnapshots(validDrivers.map(entry => entry.driver), requireNativeGroups().snapshotGroupV2(validDrivers.map(entry => entry.handle.driverId))).snapshots : [];
257
- const starts = [];
258
- for (let index = 0; index < entries.length; index += 1) {
259
- const explicit = entries[index]?.from;
260
- const presentation = explicit === undefined ? sampled[index]?.presentation ?? null : canonicalizeClipPresentation(explicit);
261
- if (presentation === null) return fail('A group start is invalid.');
262
- starts.push(presentation);
263
- }
264
- return starts;
265
- }
266
- function registerNativeGroup(controllerId, groupId, drivers, targets) {
267
- 'worklet';
268
-
269
- const records = runtimeGroupRecords();
270
- records[String(groupId)] = {
271
- controllerId,
272
- drivers
273
- };
274
- for (let index = 0; index < drivers.length; index += 1) {
275
- const handle = drivers[index]?.__smoothClipHandle;
276
- const target = targets[index];
277
- if (!handle || !target) continue;
278
- handle.ownership.value = NATIVE;
279
- handle.activeAnimationId.value = groupId;
280
- handle.presentation.value = target;
281
- }
282
- }
283
- function synchronizeGroupCompletionOnUI(controllerId, groupId, finished, driverIds) {
284
- 'worklet';
285
-
286
- const records = runtimeGroupRecords();
287
- const record = records[String(groupId)];
288
- if (!record || record.controllerId !== controllerId || record.drivers.length !== driverIds.length) {
289
- return;
290
- }
291
- for (let index = 0; index < record.drivers.length; index += 1) {
292
- if (record.drivers[index]?.__smoothClipHandle?.driverId !== driverIds[index]) {
293
- return;
294
- }
295
- }
296
- let snapshots = [];
297
- if (!finished) {
298
- try {
299
- snapshots = parseSnapshots(record.drivers, requireNativeGroups().snapshotGroupV2(record.drivers.map(driver => driver.__smoothClipHandle?.driverId ?? 0))).snapshots;
300
- } catch {
301
- snapshots = [];
302
- }
303
- }
304
- for (let index = 0; index < record.drivers.length; index += 1) {
305
- const driver = record.drivers[index];
306
- if (driver === undefined) continue;
307
- const handle = driver.__smoothClipHandle;
308
- if (handle === undefined || handle.activeAnimationId.value !== groupId) {
309
- continue;
310
- }
311
- const snapshot = snapshots[index];
312
- if (snapshot) handle.presentation.value = snapshot.presentation;
313
- handle.activeAnimationId.value = 0;
314
- handle.ownership.value = INTERACTIVE;
315
- }
316
- delete records[String(groupId)];
317
- }
318
- const pendingGroupRequests = new Map();
319
- const requestIdsByController = new Map();
320
- let nextGroupRequestId = 0;
321
- function createGroupRequest(controllerId, teardownResolution, deferCompletions = false) {
322
- nextGroupRequestId = nextGroupRequestId % 0x7ffffffe + 1;
323
- const requestId = nextGroupRequestId;
324
- let resolveRequest;
325
- let rejectRequest;
326
- const promise = new Promise((resolve, reject) => {
327
- resolveRequest = resolve;
328
- rejectRequest = reject;
329
- });
330
- pendingGroupRequests.set(requestId, {
331
- resolve: resolveRequest,
332
- reject: rejectRequest,
333
- ...(teardownResolution === undefined ? {} : {
334
- teardownValue: teardownResolution.value
335
- }),
336
- ...(deferCompletions ? {
337
- releasesCompletions: true
338
- } : {})
339
- });
340
- const ids = requestIdsByController.get(controllerId) ?? new Set();
341
- ids.add(requestId);
342
- requestIdsByController.set(controllerId, ids);
343
- if (deferCompletions) deferNativeGroupCompletions(controllerId);
344
- return {
345
- requestId,
346
- promise
347
- };
348
- }
349
- function settleGroupRequest(controllerId, requestId, succeeded, value, message) {
350
- const request = pendingGroupRequests.get(requestId);
351
- pendingGroupRequests.delete(requestId);
352
- const ids = requestIdsByController.get(controllerId);
353
- ids?.delete(requestId);
354
- if (ids?.size === 0) requestIdsByController.delete(controllerId);
355
- if (!request) return;
356
- if (succeeded) request.resolve(value);else request.reject(new Error(message));
357
- if (request.releasesCompletions) {
358
- // One turn resolves the Promise; the second lets reactions already
359
- // attached to that Promise observe settlement before callbacks run.
360
- queueMicrotask(() => {
361
- queueMicrotask(() => releaseNativeGroupCompletions(controllerId));
362
- });
363
- }
364
- }
365
- function rejectGroupRequests(controllerId) {
366
- const ids = requestIdsByController.get(controllerId);
367
- if (ids) {
368
- for (const requestId of ids) {
369
- const request = pendingGroupRequests.get(requestId);
370
- pendingGroupRequests.delete(requestId);
371
- if (!request) continue;
372
- if ('teardownValue' in request) request.resolve(request.teardownValue);else request.reject(new Error('[SmoothClipView] Group driver was destroyed.'));
373
- }
374
- requestIdsByController.delete(controllerId);
375
- }
376
- const state = nativeGroupDriverStates.get(controllerId);
377
- if (state) state.completionDeferrals = 0;
378
- }
379
- function detachNativeGroupDriverState(controllerId, state) {
380
- state.completionDeferrals = 0;
381
- const deferred = state.deferredCompletions.splice(0);
382
- if (nativeGroupDriverStates.get(controllerId) === state) {
383
- nativeGroupDriverStates.delete(controllerId);
384
- }
385
- if (deferred.length === 0) return;
386
- queueMicrotask(() => {
387
- queueMicrotask(() => {
388
- for (const result of deferred) state.callbackRef.current?.(result);
389
- });
390
- });
391
- }
392
- function queueNativeTeardownCompletion(controllerId, groupId) {
393
- const state = nativeGroupDriverStates.get(controllerId);
394
- if (!state) return;
395
- queueMicrotask(() => {
396
- queueMicrotask(() => {
397
- state.callbackRef.current?.({
398
- groupId,
399
- finished: false
400
- });
401
- });
402
- });
403
- }
404
- function finalizeNativeGroupDriverTeardown(controllerId, effectGeneration) {
405
- const state = nativeGroupDriverStates.get(controllerId);
406
- if (!state || state.effectGeneration !== effectGeneration) return;
407
- detachNativeGroupDriverState(controllerId, state);
408
- }
409
- function errorMessage(error) {
410
- 'worklet';
411
-
412
- return error instanceof Error ? error.message : '[SmoothClipView] Group operation failed.';
413
- }
414
- export function useSmoothClipGroupDriver(options = {}) {
415
- const callbackRef = useRef(options.onAnimationComplete);
416
- const stateRef = useRef(null);
417
- const groupRef = useRef(null);
418
- const disposed = useSharedValue(0);
419
- callbackRef.current = options.onAnimationComplete;
420
- if (stateRef.current === null) {
421
- stateRef.current = {
422
- callbackRef,
423
- completionDeferrals: 0,
424
- deferredCompletions: [],
425
- effectGeneration: 0
426
- };
427
- }
428
- if (groupRef.current === null) {
429
- const controllerId = allocateDriverId();
430
- const reduceMotion = reduceMotionCode(options.reduceMotion ?? 'system');
431
- const beginOnUI = drivers => {
432
- 'worklet';
433
-
434
- if (disposed.value !== 0) return fail('Group driver was destroyed.');
435
- const valid = validateDrivers(drivers);
436
- const values = requireNativeGroups().beginGroupInteractionV2(valid.map(entry => entry.handle.driverId));
437
- const parsed = parseSnapshots(drivers, values);
438
- // An unavailable wire presentation means native rejected the atomic
439
- // interaction preflight. Return readiness without changing JS ownership.
440
- if (!parsed.unavailable) applyInteractiveSnapshots(parsed.snapshots);
441
- return parsed.snapshots;
442
- };
443
- const snapshotOnUI = drivers => {
444
- 'worklet';
445
-
446
- if (disposed.value !== 0) return fail('Group driver was destroyed.');
447
- const valid = validateDrivers(drivers);
448
- return parseSnapshots(drivers, requireNativeGroups().snapshotGroupV2(valid.map(entry => entry.handle.driverId))).snapshots;
449
- };
450
- const setBatchOnUI = entries => {
451
- 'worklet';
452
-
453
- if (disposed.value !== 0) return fail('Group driver was destroyed.');
454
- const valid = validateDrivers(entries.map(entry => entry.driver));
455
- const canonical = [];
456
- const values = [];
457
- for (let index = 0; index < entries.length; index += 1) {
458
- const presentation = canonicalizeClipPresentation(entries[index]?.presentation);
459
- if (presentation === null) return fail('A batch presentation is invalid.');
460
- canonical.push(presentation);
461
- values.push(valid[index]?.handle.driverId);
462
- appendPresentation(values, presentation);
463
- }
464
- if (!requireNativeGroups().setClipPresentationBatchV2(values)) {
465
- return fail('Native rejected the complete presentation batch.');
466
- }
467
- for (const entry of valid) entry.handle.ownership.value = NATIVE;
468
- for (let index = 0; index < valid.length; index += 1) {
469
- const handle = valid[index]?.handle;
470
- const presentation = canonical[index];
471
- if (!handle || !presentation) continue;
472
- handle.activeAnimationId.value = 0;
473
- handle.presentation.value = presentation;
474
- handle.ownership.value = INTERACTIVE;
475
- }
476
- };
477
- const animateOnUI = (entries, animation) => {
478
- 'worklet';
479
-
480
- if (disposed.value !== 0) return fail('Group driver was destroyed.');
481
- if (!animationIsFinite(animation)) return fail('Group animation is invalid.');
482
- const drivers = entries.map(entry => entry.driver);
483
- const valid = validateDrivers(drivers);
484
- const targets = [];
485
- for (const entry of entries) {
486
- const target = canonicalizeClipPresentation(entry.target);
487
- if (target === null) return fail('A group target is invalid.');
488
- targets.push(target);
489
- }
490
- const starts = snapshotsForMotionStarts(valid, entries);
491
- for (let index = 0; index < starts.length; index += 1) {
492
- if (starts[index]?.clip.curve !== targets[index]?.clip.curve) {
493
- return fail('Curve-changing animations must use streamed rendering.');
494
- }
495
- }
496
- const native = requireNativeGroups();
497
- const values = [];
498
- let groupId = 0;
499
- if (animation.type === 'keyframes') {
500
- for (let index = 0; index < entries.length; index += 1) {
501
- const entry = entries[index];
502
- const target = targets[index];
503
- const start = starts[index];
504
- const frames = [];
505
- let previousOffset = -1;
506
- for (const frame of entry.frames) {
507
- const canonical = canonicalizeClipPresentation(frame.presentation);
508
- const presentation = frames.length === 0 && entry.from === undefined ? start : canonical;
509
- if (canonical === null || presentation === null || !Number.isFinite(frame.offset) || frame.offset <= previousOffset || frame.offset < 0 || frame.offset > 1 || presentation.clip.curve !== target.clip.curve) {
510
- return fail('Group keyframes are inconsistent.');
511
- }
512
- previousOffset = frame.offset;
513
- frames.push({
514
- offset: frame.offset,
515
- presentation
516
- });
517
- }
518
- if (frames.length < 2 || frames[0]?.offset !== 0 || frames[frames.length - 1]?.offset !== 1 || !clipPresentationEquals(frames[frames.length - 1]?.presentation ?? null, target) || entry.from !== undefined && !clipPresentationEquals(frames[0]?.presentation ?? null, start)) {
519
- return fail('Group keyframes are inconsistent.');
520
- }
521
- values.push(valid[index]?.handle.driverId);
522
- values.push(entry.from === undefined ? 0 : 1);
523
- appendPresentation(values, start);
524
- appendPresentation(values, target);
525
- values.push(frames.length);
526
- for (const frame of frames) {
527
- values.push(frame.offset);
528
- appendPresentation(values, frame.presentation);
529
- }
530
- }
531
- groupId = native.animateKeyframesGroupV2(controllerId, values, animation.duration, reduceMotion, suspensionPolicyCode(animation.suspensionPolicy), animationStartTimestamp());
532
- } else {
533
- for (let index = 0; index < entries.length; index += 1) {
534
- const entry = entries[index];
535
- values.push(valid[index]?.handle.driverId);
536
- values.push(entry.from === undefined ? 0 : 1);
537
- appendPresentation(values, starts[index]);
538
- appendPresentation(values, targets[index]);
539
- }
540
- if (values.length !== entries.length * MOTION_ENTRY_STRIDE) {
541
- return fail('Internal group wire encoding failed.');
542
- }
543
- if (animation.type === 'timing') {
544
- const [x1, y1, x2, y2] = animation.controlPoints;
545
- groupId = native.animateTimingGroupV2(controllerId, values, animation.duration, x1, y1, x2, y2, reduceMotion, suspensionPolicyCode(animation.suspensionPolicy), animationStartTimestamp());
546
- } else {
547
- if (!springScaleIsProvablyPositive(starts, targets, animation)) {
548
- return fail('This scale-changing spring is not provably positive; compile it to keyframes.');
549
- }
550
- const inheritVelocity = animation.initialVelocity === undefined || animation.initialVelocity === 'inherit';
551
- groupId = native.animateSpringGroupV2(controllerId, values, animation.mass ?? 1, animation.stiffness ?? 100, animation.damping ?? 10, inheritVelocity ? 0 : animation.initialVelocity, inheritVelocity, reduceMotion, suspensionPolicyCode(animation.suspensionPolicy), animationStartTimestamp());
552
- }
553
- }
554
- if (!Number.isInteger(groupId) || groupId <= 0) {
555
- return fail('Native rejected the complete group animation.');
556
- }
557
- registerNativeGroup(controllerId, groupId, drivers, targets);
558
- return groupId;
559
- };
560
- const cancelOnUI = (groupId, behavior = 'freeze') => {
561
- 'worklet';
562
-
563
- if (disposed.value !== 0) return fail('Group driver was destroyed.');
564
- if (!Number.isSafeInteger(groupId) || groupId <= 0) {
565
- return fail('A group ID must be a positive integer.');
566
- }
567
- if (behavior !== 'freeze' && behavior !== 'finish') {
568
- return fail('Group cancel behavior is invalid.');
569
- }
570
- const records = runtimeGroupRecords();
571
- const record = records[String(groupId)];
572
- if (!record || record.controllerId !== controllerId) return [];
573
- const values = requireNativeGroups().cancelAnimationGroupV2(groupId, behavior === 'finish' ? 1 : 0);
574
- if (values.length === 0) {
575
- for (const driver of record.drivers) {
576
- const handle = driver.__smoothClipHandle;
577
- if (!handle || handle.activeAnimationId.value !== groupId) continue;
578
- handle.activeAnimationId.value = 0;
579
- handle.ownership.value = INTERACTIVE;
580
- }
581
- delete records[String(groupId)];
582
- return [];
583
- }
584
- const snapshots = parseSnapshots(record.drivers, values).snapshots;
585
- applyInteractiveSnapshots(snapshots);
586
- delete records[String(groupId)];
587
- return snapshots;
588
- };
589
- const group = {
590
- kind: 'group',
591
- ui: {
592
- beginInteraction(drivers) {
593
- 'worklet';
594
-
595
- if (isRNRuntime()) return uiOnly();
596
- return beginOnUI(drivers);
597
- },
598
- snapshotCurrent(drivers) {
599
- 'worklet';
600
-
601
- if (isRNRuntime()) return uiOnly();
602
- return snapshotOnUI(drivers);
603
- },
604
- setBatch(entries) {
605
- 'worklet';
606
-
607
- if (isRNRuntime()) return uiOnly();
608
- setBatchOnUI(entries);
609
- },
610
- animateTo(entries, animation) {
611
- 'worklet';
612
-
613
- if (isRNRuntime()) return uiOnly();
614
- return animateOnUI(entries, animation);
615
- },
616
- cancel(groupId, behavior = 'freeze') {
617
- 'worklet';
618
-
619
- if (isRNRuntime()) return uiOnly();
620
- return cancelOnUI(groupId, behavior);
621
- }
622
- },
623
- react: {
624
- beginInteraction(drivers) {
625
- const {
626
- requestId,
627
- promise
628
- } = createGroupRequest(controllerId, undefined, true);
629
- scheduleOnUI(() => {
630
- 'worklet';
631
-
632
- try {
633
- const result = beginOnUI(drivers);
634
- scheduleOnRN(settleGroupRequest, controllerId, requestId, true, result, '');
635
- } catch (error) {
636
- scheduleOnRN(settleGroupRequest, controllerId, requestId, false, undefined, errorMessage(error));
637
- }
638
- });
639
- return promise;
640
- },
641
- snapshotCurrent(drivers) {
642
- const {
643
- requestId,
644
- promise
645
- } = createGroupRequest(controllerId);
646
- scheduleOnUI(() => {
647
- 'worklet';
648
-
649
- try {
650
- const result = snapshotOnUI(drivers);
651
- scheduleOnRN(settleGroupRequest, controllerId, requestId, true, result, '');
652
- } catch (error) {
653
- scheduleOnRN(settleGroupRequest, controllerId, requestId, false, undefined, errorMessage(error));
654
- }
655
- });
656
- return promise;
657
- },
658
- setBatch(entries) {
659
- const {
660
- requestId,
661
- promise
662
- } = createGroupRequest(controllerId, {
663
- value: undefined
664
- }, true);
665
- scheduleOnUI(() => {
666
- 'worklet';
667
-
668
- try {
669
- setBatchOnUI(entries);
670
- scheduleOnRN(settleGroupRequest, controllerId, requestId, true, undefined, '');
671
- } catch (error) {
672
- scheduleOnRN(settleGroupRequest, controllerId, requestId, false, undefined, errorMessage(error));
673
- }
674
- });
675
- return promise;
676
- },
677
- animateTo(entries, animation) {
678
- const {
679
- requestId,
680
- promise
681
- } = createGroupRequest(controllerId, {
682
- value: 0
683
- }, true);
684
- scheduleOnUI(() => {
685
- 'worklet';
686
-
687
- try {
688
- const result = animateOnUI(entries, animation);
689
- scheduleOnRN(settleGroupRequest, controllerId, requestId, true, result, '');
690
- } catch (error) {
691
- scheduleOnRN(settleGroupRequest, controllerId, requestId, false, undefined, errorMessage(error));
692
- }
693
- });
694
- return promise;
695
- },
696
- cancel(groupId, behavior = 'freeze') {
697
- const {
698
- requestId,
699
- promise
700
- } = createGroupRequest(controllerId, undefined, true);
701
- scheduleOnUI(() => {
702
- 'worklet';
703
-
704
- try {
705
- const result = cancelOnUI(groupId, behavior);
706
- scheduleOnRN(settleGroupRequest, controllerId, requestId, true, result, '');
707
- } catch (error) {
708
- scheduleOnRN(settleGroupRequest, controllerId, requestId, false, undefined, errorMessage(error));
709
- }
710
- });
711
- return promise;
712
- }
713
- }
714
- };
715
- controllerIds.set(group, controllerId);
716
- groupRef.current = group;
717
- }
718
- useEffect(() => {
719
- disposed.value = 0;
720
- const controllerId = controllerIdFor(groupRef.current);
721
- const state = stateRef.current;
722
- if (state === null) return undefined;
723
- state.effectGeneration += 1;
724
- const effectGeneration = state.effectGeneration;
725
- nativeGroupDriverStates.set(controllerId, state);
726
- let subscription;
727
- if (GROUPS_SUPPORTED) {
728
- try {
729
- subscription = nativeGroups.onClipGroupAnimationComplete?.(result => {
730
- if (result.controllerId !== controllerId) return;
731
- scheduleOnUI(synchronizeGroupCompletionOnUI, result.controllerId, result.groupId, result.finished, result.driverIds);
732
- deliverNativeGroupCompletion(controllerId, result.groupId, result.finished);
733
- });
734
- } catch {
735
- // A V1 native module may expose neither the event hook nor a compatible
736
- // emitter facade. Unsupported setup is a no-op; UI methods still report
737
- // the normal capability error when invoked.
738
- }
739
- }
740
- return () => {
741
- try {
742
- subscription?.remove();
743
- } catch {
744
- // Older native event subscriptions are not guaranteed to support V2
745
- // teardown. Cleanup must remain safe across a new-JS/V1-native pair.
746
- }
747
- rejectGroupRequests(controllerId);
748
- scheduleOnUI((ownerId, generation, gone) => {
749
- 'worklet';
750
-
751
- gone.value = 1;
752
- const records = runtimeGroupRecords();
753
- const native = GROUPS_SUPPORTED ? requireNativeGroups() : null;
754
- for (const key of Object.keys(records)) {
755
- const record = records[key];
756
- if (record?.controllerId !== ownerId) continue;
757
- const groupId = Number(key);
758
- const values = native?.cancelAnimationGroupV2(groupId, 0) ?? [];
759
- let snapshots = [];
760
- if (values.length !== 0) {
761
- try {
762
- snapshots = parseSnapshots(record.drivers, values).snapshots;
763
- } catch {
764
- snapshots = [];
765
- }
766
- }
767
- for (let index = 0; index < record.drivers.length; index += 1) {
768
- const handle = record.drivers[index]?.__smoothClipHandle;
769
- if (!handle || handle.activeAnimationId.value !== groupId) continue;
770
- const snapshot = snapshots[index];
771
- if (snapshot) handle.presentation.value = snapshot.presentation;
772
- handle.activeAnimationId.value = 0;
773
- handle.ownership.value = INTERACTIVE;
774
- }
775
- delete records[key];
776
- if (values.length !== 0) {
777
- scheduleOnRN(queueNativeTeardownCompletion, ownerId, groupId);
778
- }
779
- }
780
- scheduleOnRN(finalizeNativeGroupDriverTeardown, ownerId, generation);
781
- }, controllerId, effectGeneration, disposed);
782
- };
783
- }, [disposed]);
784
- return groupRef.current;
785
- }
786
-
787
- // The public object intentionally has no internal fields. Keeping the
788
- // controller association in a WeakMap preserves that surface while allowing
789
- // StrictMode effect replays to reuse the same native callback owner.
790
- const controllerIds = new WeakMap();
791
- function controllerIdFor(group) {
792
- if (group === null) return 0;
793
- const existing = controllerIds.get(group);
794
- if (existing !== undefined) return existing;
795
- const id = allocateDriverId();
796
- controllerIds.set(group, id);
797
- return id;
798
- }
799
- //# sourceMappingURL=groupDrivers.native.js.map