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
@@ -16,7 +16,9 @@
16
16
  #import <react/renderer/components/SmoothClipViewSpec/Props.h>
17
17
  #import <react/renderer/components/SmoothClipViewSpec/RCTComponentViewHelpers.h>
18
18
 
19
+ #include <algorithm>
19
20
  #include <cfloat>
21
+ #include <cmath>
20
22
 
21
23
  using namespace facebook::react;
22
24
 
@@ -80,7 +82,7 @@ static SmoothClipCornerCurve SmoothClipPresentationCurve(
80
82
  : SmoothClipCornerCurveCircular;
81
83
  }
82
84
 
83
- static bool SmoothClipBuildPresentationV2(
85
+ static bool SmoothClipBuildPresentation(
84
86
  double x,
85
87
  double y,
86
88
  double width,
@@ -93,13 +95,30 @@ static bool SmoothClipBuildPresentationV2(
93
95
  double contentTranslateX,
94
96
  double contentTranslateY,
95
97
  double contentScale,
98
+ BOOL shadowEnabled,
99
+ double shadowRed,
100
+ double shadowGreen,
101
+ double shadowBlue,
102
+ double shadowAlpha,
103
+ double shadowOffsetX,
104
+ double shadowOffsetY,
105
+ double shadowBlurRadius,
106
+ double shadowSpreadDistance,
96
107
  smoothclip::Presentation *result) {
97
108
  if (result == nullptr || !isfinite(x) || !isfinite(y) ||
98
109
  !isfinite(width) || !isfinite(height) ||
99
110
  !isfinite(topLeftRadius) || !isfinite(topRightRadius) ||
100
111
  !isfinite(bottomRightRadius) || !isfinite(bottomLeftRadius) ||
101
112
  !isfinite(contentTranslateX) || !isfinite(contentTranslateY) ||
102
- !isfinite(contentScale) || contentScale <= 0 ||
113
+ !isfinite(contentScale) || !isfinite(shadowRed) ||
114
+ !isfinite(shadowGreen) || !isfinite(shadowBlue) ||
115
+ !isfinite(shadowAlpha) ||
116
+ !isfinite(shadowOffsetX) || !isfinite(shadowOffsetY) ||
117
+ !isfinite(shadowBlurRadius) || !isfinite(shadowSpreadDistance) ||
118
+ contentScale <= 0 || shadowRed < 0 || shadowRed > 1 ||
119
+ shadowGreen < 0 || shadowGreen > 1 ||
120
+ shadowBlue < 0 || shadowBlue > 1 ||
121
+ shadowAlpha < 0 || shadowAlpha > 1 || shadowBlurRadius < 0 ||
103
122
  (curveCode != static_cast<NSInteger>(smoothclip::ClipCurve::Circular) &&
104
123
  curveCode != static_cast<NSInteger>(smoothclip::ClipCurve::Continuous))) {
105
124
  return false;
@@ -115,11 +134,121 @@ static bool SmoothClipBuildPresentationV2(
115
134
  geometry.bottomRightRadius = bottomRightRadius;
116
135
  geometry.bottomLeftRadius = bottomLeftRadius;
117
136
  geometry.curve = static_cast<smoothclip::ClipCurve>(curveCode);
137
+ const smoothclip::Shadow shadow{
138
+ shadowEnabled,
139
+ shadowRed,
140
+ shadowGreen,
141
+ shadowBlue,
142
+ shadowAlpha,
143
+ shadowOffsetX,
144
+ shadowOffsetY,
145
+ shadowBlurRadius,
146
+ shadowSpreadDistance};
118
147
  *result = {
119
- geometry, contentTranslateX, contentTranslateY, contentScale};
148
+ geometry, contentTranslateX, contentTranslateY, contentScale, shadow};
120
149
  return true;
121
150
  }
122
151
 
152
+ static bool SmoothBoxShadowEqual(
153
+ const smoothclip::Shadow &first,
154
+ const smoothclip::Shadow &second) {
155
+ return first.enabled == second.enabled &&
156
+ first.red == second.red && first.green == second.green &&
157
+ first.blue == second.blue && first.alpha == second.alpha &&
158
+ first.offsetX == second.offsetX && first.offsetY == second.offsetY &&
159
+ first.blurRadius == second.blurRadius &&
160
+ first.spreadDistance == second.spreadDistance;
161
+ }
162
+
163
+ static bool SmoothBoxShadowVisible(
164
+ SmoothClipCanonicalGeometry geometry,
165
+ const smoothclip::Shadow &shadow) {
166
+ return shadow.enabled && shadow.alpha > 0 && !CGRectIsEmpty(geometry.rect);
167
+ }
168
+
169
+ static smoothclip::Shadow SmoothBoxShadowWithEffectiveAlpha(
170
+ SmoothClipCanonicalGeometry geometry,
171
+ smoothclip::Shadow shadow) {
172
+ if (!SmoothBoxShadowVisible(geometry, shadow)) {
173
+ shadow.alpha = 0;
174
+ }
175
+ return shadow;
176
+ }
177
+
178
+ static void SmoothBoxShadowNormalizeAnimationEndpoints(
179
+ SmoothClipCanonicalGeometry fromGeometry,
180
+ smoothclip::Shadow *fromShadow,
181
+ SmoothClipCanonicalGeometry toGeometry,
182
+ smoothclip::Shadow *toShadow) {
183
+ const bool fromVisible = SmoothBoxShadowVisible(fromGeometry, *fromShadow);
184
+ const bool toVisible = SmoothBoxShadowVisible(toGeometry, *toShadow);
185
+ if (fromVisible == toVisible) {
186
+ *fromShadow = SmoothBoxShadowWithEffectiveAlpha(
187
+ fromGeometry, *fromShadow);
188
+ *toShadow = SmoothBoxShadowWithEffectiveAlpha(toGeometry, *toShadow);
189
+ return;
190
+ }
191
+
192
+ smoothclip::Shadow *invisible = fromVisible ? toShadow : fromShadow;
193
+ const smoothclip::Shadow &visible = fromVisible ? *fromShadow : *toShadow;
194
+ invisible->enabled = true;
195
+ invisible->red = visible.red;
196
+ invisible->green = visible.green;
197
+ invisible->blue = visible.blue;
198
+ invisible->alpha = 0;
199
+ invisible->offsetX = visible.offsetX;
200
+ invisible->offsetY = visible.offsetY;
201
+ invisible->blurRadius = visible.blurRadius;
202
+ invisible->spreadDistance = visible.spreadDistance;
203
+ }
204
+
205
+ static bool SmoothBoxShadowColorEqual(
206
+ const smoothclip::Shadow &first,
207
+ const smoothclip::Shadow &second) {
208
+ return first.red == second.red && first.green == second.green &&
209
+ first.blue == second.blue && first.alpha == second.alpha;
210
+ }
211
+
212
+ static bool SmoothBoxShadowPathInputEqual(
213
+ SmoothClipCanonicalGeometry firstGeometry,
214
+ const smoothclip::Shadow &firstShadow,
215
+ SmoothClipCanonicalGeometry secondGeometry,
216
+ const smoothclip::Shadow &secondShadow) {
217
+ return CGRectEqualToRect(firstGeometry.rect, secondGeometry.rect) &&
218
+ SmoothClipCornerRadiiEqual(
219
+ firstGeometry.radii, secondGeometry.radii) &&
220
+ firstGeometry.curve == secondGeometry.curve &&
221
+ firstShadow.spreadDistance == secondShadow.spreadDistance;
222
+ }
223
+
224
+ static CGFloat SmoothClipAdjustedShadowRadius(
225
+ CGFloat radius,
226
+ CGFloat spread) {
227
+ const CGFloat magnitude = ABS(spread);
228
+ CGFloat multiplier = 1;
229
+ if (magnitude > 0 && radius < magnitude) {
230
+ multiplier = 1 + pow(radius / magnitude - 1, 3);
231
+ }
232
+ return MAX(0, radius + spread * multiplier);
233
+ }
234
+
235
+ static CGPathRef SmoothClipCreateShadowPath(
236
+ SmoothClipCanonicalGeometry geometry,
237
+ const smoothclip::Shadow &shadow) {
238
+ const CGFloat spread = shadow.spreadDistance;
239
+ CGRect rect = CGRectInset(geometry.rect, -spread, -spread);
240
+ if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0) {
241
+ rect = CGRectZero;
242
+ }
243
+ const SmoothClipCornerRadii radii = {
244
+ SmoothClipAdjustedShadowRadius(geometry.radii.topLeft, spread),
245
+ SmoothClipAdjustedShadowRadius(geometry.radii.topRight, spread),
246
+ SmoothClipAdjustedShadowRadius(geometry.radii.bottomRight, spread),
247
+ SmoothClipAdjustedShadowRadius(geometry.radii.bottomLeft, spread),
248
+ };
249
+ return SmoothClipCreateRoundedRectPath(rect, radii, geometry.curve);
250
+ }
251
+
123
252
  static NSString *SmoothClipCALayerCornerCurve(
124
253
  SmoothClipCornerCurve curve) API_AVAILABLE(ios(13.0)) {
125
254
  return curve == SmoothClipCornerCurveContinuous
@@ -191,24 +320,38 @@ static std::array<double, 11> SmoothClipVelocityChannels(
191
320
  contentScale};
192
321
  }
193
322
 
323
+ static BOOL SmoothClipRectIntersectsHost(CGRect rect, CGRect host) {
324
+ const CGRect intersection = CGRectIntersection(rect, host);
325
+ return !CGRectIsNull(intersection) && !CGRectIsEmpty(intersection);
326
+ }
327
+
328
+ static smoothclip::Presentation SmoothClipCanonicalSnapshot(
329
+ smoothclip::Presentation presentation) {
330
+ smoothclip::canonicalizePresentation(presentation);
331
+ return presentation;
332
+ }
333
+
194
334
  @implementation SmoothClipView {
335
+ CALayer *_shadowLayer;
195
336
  SmoothClipContainerView *_clipContainer;
196
337
  SmoothClipContainerView *_contentContainer;
197
338
  CAShapeLayer *_unequalCornerMask;
198
339
  CGRect _requestedClip;
199
- CGRect _normalizedClip;
340
+ CGRect _canonicalClip;
200
341
  CGFloat _requestedRadius;
201
- CGFloat _normalizedRadius;
342
+ CGFloat _canonicalRadius;
202
343
  SmoothClipCornerRadii _requestedRadii;
203
- SmoothClipCornerRadii _normalizedRadii;
344
+ SmoothClipCornerRadii _canonicalRadii;
204
345
  SmoothClipCornerCurve _requestedCurve;
205
- SmoothClipCornerCurve _normalizedCurve;
346
+ SmoothClipCornerCurve _canonicalCurve;
206
347
  BOOL _requestedHasExplicitRadii;
207
- BOOL _normalizedHasExplicitRadii;
348
+ BOOL _canonicalHasExplicitRadii;
208
349
  CGPoint _requestedContentTranslation;
209
- CGPoint _normalizedContentTranslation;
350
+ CGPoint _canonicalContentTranslation;
210
351
  CGFloat _requestedContentScale;
211
- CGFloat _normalizedContentScale;
352
+ CGFloat _canonicalContentScale;
353
+ smoothclip::Shadow _requestedShadow;
354
+ smoothclip::Shadow _canonicalShadow;
212
355
  uint64_t _driverId;
213
356
  BOOL _hasLayout;
214
357
  BOOL _commandIsAuthoritative;
@@ -219,23 +362,11 @@ static std::array<double, 11> SmoothClipVelocityChannels(
219
362
 
220
363
  int32_t _activeAnimationId;
221
364
  NSInteger _activeAnimationKind;
222
- // Set when a native animation install arrived before the first layout.
223
- // The install is deferred and re-run through the registry at first layout
224
- // so it joins from live presentation geometry with the true remaining time.
225
- BOOL _pendingAnimationInstall;
226
- CFTimeInterval _animationStartedAt;
227
- CFTimeInterval _animationDuration;
228
- // Bumped on every CA install. updateLayoutMetrics captures it on entry so a
229
- // registry-driven install that happens inside its own lifecycle notification
230
- // (a latch resume during that very layout pass) is detectable: the local
231
- // rebuild must then be skipped, because its captured remaining time predates
232
- // the suspension and would shorten — or zero out — the fresh install.
233
- uint32_t _animationInstallGeneration;
234
365
  smoothclip::TimingAnimation _timingAnimation;
235
366
  smoothclip::SpringAnimation _springAnimation;
236
367
  SmoothClipAnimationDelegate *_animationDelegate;
237
368
 
238
- // 'inherit' velocity samples (normalized geometry, per view); recording/
369
+ // 'inherit' velocity samples (raw canonical geometry, per view); recording/
239
370
  // coalescing/projection live in the shared cpp/SmoothClipVelocityTracker.h
240
371
  // (behavior-paired with Android's per-driver history).
241
372
  smoothclip::VelocitySampleHistory _velocitySamples;
@@ -262,6 +393,9 @@ static std::array<double, 11> SmoothClipVelocityChannels(
262
393
  static const auto defaultProps =
263
394
  std::make_shared<const SmoothClipViewProps>();
264
395
  _props = defaultProps;
396
+ // SmoothClipView is the fixed viewport. Consumer overflow props are
397
+ // intentionally overridden again after every prop update below.
398
+ self.clipsToBounds = YES;
265
399
 
266
400
  _clipContainer = [[SmoothClipContainerView alloc] initWithFrame:CGRectZero];
267
401
  _clipContainer.layer.masksToBounds = YES;
@@ -277,6 +411,9 @@ static std::array<double, 11> SmoothClipVelocityChannels(
277
411
  [_clipContainer addSubview:_contentContainer];
278
412
  [self addSubview:_clipContainer];
279
413
 
414
+ _shadowLayer = nil;
415
+ // Created lazily so the common clipping-only path owns no shadow layer.
416
+
280
417
  _unequalCornerMask = [CAShapeLayer layer];
281
418
  _unequalCornerMask.actions = @{
282
419
  @"path": [NSNull null],
@@ -287,27 +424,27 @@ static std::array<double, 11> SmoothClipVelocityChannels(
287
424
  _unequalCornerMask.needsDisplayOnBoundsChange = NO;
288
425
 
289
426
  _requestedClip = CGRectZero;
290
- _normalizedClip = CGRectZero;
427
+ _canonicalClip = CGRectZero;
291
428
  _requestedRadius = 0;
292
- _normalizedRadius = 0;
429
+ _canonicalRadius = 0;
293
430
  _requestedRadii = {0, 0, 0, 0};
294
- _normalizedRadii = {0, 0, 0, 0};
431
+ _canonicalRadii = {0, 0, 0, 0};
295
432
  _requestedCurve = SmoothClipCornerCurveCircular;
296
- _normalizedCurve = SmoothClipCornerCurveCircular;
433
+ _canonicalCurve = SmoothClipCornerCurveCircular;
297
434
  _requestedHasExplicitRadii = NO;
298
- _normalizedHasExplicitRadii = NO;
435
+ _canonicalHasExplicitRadii = NO;
299
436
  _requestedContentTranslation = CGPointZero;
300
- _normalizedContentTranslation = CGPointZero;
437
+ _canonicalContentTranslation = CGPointZero;
301
438
  _requestedContentScale = 1;
302
- _normalizedContentScale = 1;
439
+ _canonicalContentScale = 1;
440
+ _requestedShadow = {};
441
+ _canonicalShadow = {};
303
442
  _driverId = 0;
304
443
  _hasLayout = NO;
305
444
  _commandIsAuthoritative = NO;
306
445
  _ignoreAnimationCallback = NO;
307
446
  _activeAnimationId = 0;
308
447
  _activeAnimationKind = 0;
309
- _pendingAnimationInstall = NO;
310
- _animationInstallGeneration = 0;
311
448
  smoothclip::clearVelocitySamples(_velocitySamples);
312
449
  // App-state transitions are observed by the registry itself (see
313
450
  // installApplicationStateObservers): the active flag is process-global,
@@ -364,9 +501,61 @@ static std::array<double, 11> SmoothClipVelocityChannels(
364
501
  presentation.contentTranslateX,
365
502
  presentation.contentTranslateY);
366
503
  _requestedContentScale = presentation.contentScale;
504
+ _requestedShadow = presentation.shadow;
505
+ }
506
+
507
+ - (CGColorRef)colorForShadow:(const smoothclip::Shadow &)shadow {
508
+ return [UIColor colorWithRed:smoothclip::clamp01(shadow.red)
509
+ green:smoothclip::clamp01(shadow.green)
510
+ blue:smoothclip::clamp01(shadow.blue)
511
+ alpha:smoothclip::clamp01(shadow.alpha)].CGColor;
512
+ }
513
+
514
+ - (CALayer *)ensureShadowLayer {
515
+ if (_shadowLayer != nil) return _shadowLayer;
516
+ _shadowLayer = [CALayer layer];
517
+ _shadowLayer.masksToBounds = NO;
518
+ _shadowLayer.actions = @{
519
+ @"shadowPath": [NSNull null],
520
+ @"shadowColor": [NSNull null],
521
+ @"shadowOpacity": [NSNull null],
522
+ @"shadowRadius": [NSNull null],
523
+ @"shadowOffset": [NSNull null],
524
+ @"bounds": [NSNull null],
525
+ @"position": [NSNull null],
526
+ };
527
+ [self.layer insertSublayer:_shadowLayer below:_clipContainer.layer];
528
+ return _shadowLayer;
529
+ }
530
+
531
+ - (void)writeShadowModel:(const smoothclip::Shadow &)shadow
532
+ geometry:(SmoothClipCanonicalGeometry)geometry {
533
+ const smoothclip::Shadow effectiveShadow =
534
+ SmoothBoxShadowWithEffectiveAlpha(geometry, shadow);
535
+ const BOOL visible = SmoothBoxShadowVisible(geometry, effectiveShadow);
536
+ if (_shadowLayer == nil && !visible) return;
537
+
538
+ CALayer *shadowLayer = visible ? [self ensureShadowLayer] : _shadowLayer;
539
+ shadowLayer.bounds = self.bounds;
540
+ shadowLayer.position = CGPointMake(
541
+ CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
542
+ CGPathRef path = SmoothClipCreateShadowPath(geometry, effectiveShadow);
543
+ shadowLayer.shadowPath = path;
544
+ CGPathRelease(path);
545
+ shadowLayer.shadowColor = [self colorForShadow:effectiveShadow];
546
+ shadowLayer.shadowOpacity = visible ? 1 : 0;
547
+ shadowLayer.shadowRadius = MAX(0, effectiveShadow.blurRadius) / 2.0;
548
+ shadowLayer.shadowOffset = CGSizeMake(
549
+ effectiveShadow.offsetX, effectiveShadow.offsetY);
367
550
  }
368
551
 
369
- - (BOOL)normalizedRequestedGeometry:(SmoothNormalizedClipGeometry *)geometry {
552
+ - (void)writeShadow:(const smoothclip::Shadow &)shadow
553
+ geometry:(SmoothClipCanonicalGeometry)geometry {
554
+ _canonicalShadow = shadow;
555
+ [self writeShadowModel:shadow geometry:geometry];
556
+ }
557
+
558
+ - (BOOL)canonicalRequestedGeometry:(SmoothClipCanonicalGeometry *)geometry {
370
559
  if (!_hasLayout) return NO;
371
560
  #if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
372
561
  const bool signpostsEnabled =
@@ -375,11 +564,10 @@ static std::array<double, 11> SmoothClipVelocityChannels(
375
564
  if (signpostsEnabled) {
376
565
  identifier = os_signpost_id_generate([SmoothClipView signpostLog]);
377
566
  os_signpost_interval_begin(
378
- [SmoothClipView signpostLog], identifier, "normalization");
567
+ [SmoothClipView signpostLog], identifier, "canonicalization");
379
568
  }
380
569
  #endif
381
- const CGSize hostSize = self.bounds.size;
382
- const BOOL valid = SmoothClipNormalizeGeometryV2(
570
+ const BOOL valid = SmoothClipCanonicalizeGeometry(
383
571
  CGRectGetMinX(_requestedClip),
384
572
  CGRectGetMinY(_requestedClip),
385
573
  CGRectGetWidth(_requestedClip),
@@ -389,12 +577,11 @@ static std::array<double, 11> SmoothClipVelocityChannels(
389
577
  _requestedRadii.bottomRight,
390
578
  _requestedRadii.bottomLeft,
391
579
  _requestedCurve,
392
- CGSizeMake(MAX(0, hostSize.width), MAX(0, hostSize.height)),
393
580
  geometry);
394
581
  #if defined(SMOOTH_CLIP_ENABLE_SIGNPOSTS) && SMOOTH_CLIP_ENABLE_SIGNPOSTS
395
582
  if (signpostsEnabled) {
396
583
  os_signpost_interval_end(
397
- [SmoothClipView signpostLog], identifier, "normalization");
584
+ [SmoothClipView signpostLog], identifier, "canonicalization");
398
585
  }
399
586
  #endif
400
587
  return valid;
@@ -409,15 +596,15 @@ static std::array<double, 11> SmoothClipVelocityChannels(
409
596
 
410
597
  - (void)syncVisibilityForRect:(CGRect)rect {
411
598
  [self setClipContainerHidden:CGRectIsEmpty(rect)];
599
+ _clipContainer.accessibilityElementsHidden =
600
+ !SmoothClipRectIntersectsHost(rect, self.bounds);
412
601
  }
413
602
 
414
603
  - (void)configureUnequalCornerMaskForGeometry:
415
- (SmoothNormalizedClipGeometry)geometry {
416
- const CGSize hostSize = self.bounds.size;
417
- _unequalCornerMask.bounds = CGRectMake(
418
- 0, 0, MAX(0, hostSize.width), MAX(0, hostSize.height));
604
+ (SmoothClipCanonicalGeometry)geometry {
605
+ _unequalCornerMask.bounds = geometry.rect;
419
606
  _unequalCornerMask.position = CGPointMake(
420
- MAX(0, hostSize.width) / 2, MAX(0, hostSize.height) / 2);
607
+ CGRectGetMidX(geometry.rect), CGRectGetMidY(geometry.rect));
421
608
  CGPathRef path = SmoothClipCreateRoundedRectPath(
422
609
  geometry.rect, geometry.radii, geometry.curve);
423
610
  _unequalCornerMask.path = path;
@@ -425,7 +612,7 @@ static std::array<double, 11> SmoothClipVelocityChannels(
425
612
  }
426
613
 
427
614
  - (void)applyStaticCornerRepresentation:
428
- (SmoothNormalizedClipGeometry)geometry {
615
+ (SmoothClipCanonicalGeometry)geometry {
429
616
  CALayer *layer = _clipContainer.layer;
430
617
  if (SmoothClipCornerRadiiAreUniform(geometry.radii)) {
431
618
  layer.mask = nil;
@@ -439,29 +626,29 @@ static std::array<double, 11> SmoothClipVelocityChannels(
439
626
  }
440
627
  }
441
628
 
442
- - (SmoothNormalizedClipGeometry)normalizedGeometryValue {
629
+ - (SmoothClipCanonicalGeometry)canonicalGeometryValue {
443
630
  return {
444
- .rect = _normalizedClip,
445
- .radius = _normalizedRadius,
446
- .radii = _normalizedRadii,
447
- .curve = _normalizedCurve,
631
+ .rect = _canonicalClip,
632
+ .radius = _canonicalRadius,
633
+ .radii = _canonicalRadii,
634
+ .curve = _canonicalCurve,
448
635
  };
449
636
  }
450
637
 
451
- - (void)writeLayerGeometry:(SmoothNormalizedClipGeometry)geometry
638
+ - (void)writeLayerGeometry:(SmoothClipCanonicalGeometry)geometry
452
639
  hasExplicitRadii:(BOOL)hasExplicitRadii {
453
640
  const BOOL rectChanged =
454
- !CGRectEqualToRect(_normalizedClip, geometry.rect);
641
+ !CGRectEqualToRect(_canonicalClip, geometry.rect);
455
642
  const BOOL radiiChanged =
456
- !SmoothClipCornerRadiiEqual(_normalizedRadii, geometry.radii);
457
- const BOOL curveChanged = _normalizedCurve != geometry.curve;
643
+ !SmoothClipCornerRadiiEqual(_canonicalRadii, geometry.radii);
644
+ const BOOL curveChanged = _canonicalCurve != geometry.curve;
458
645
  const BOOL representationChanged =
459
- _normalizedHasExplicitRadii != hasExplicitRadii;
460
- _normalizedClip = geometry.rect;
461
- _normalizedRadius = geometry.radius;
462
- _normalizedRadii = geometry.radii;
463
- _normalizedCurve = geometry.curve;
464
- _normalizedHasExplicitRadii = hasExplicitRadii;
646
+ _canonicalHasExplicitRadii != hasExplicitRadii;
647
+ _canonicalClip = geometry.rect;
648
+ _canonicalRadius = geometry.radius;
649
+ _canonicalRadii = geometry.radii;
650
+ _canonicalCurve = geometry.curve;
651
+ _canonicalHasExplicitRadii = hasExplicitRadii;
465
652
  if (!rectChanged && !radiiChanged && !curveChanged &&
466
653
  !representationChanged) {
467
654
  return;
@@ -496,28 +683,33 @@ static std::array<double, 11> SmoothClipVelocityChannels(
496
683
 
497
684
  - (void)writeContentTranslation:(CGPoint)translation
498
685
  scale:(CGFloat)scale {
499
- if (CGPointEqualToPoint(_normalizedContentTranslation, translation) &&
500
- _normalizedContentScale == scale) {
686
+ if (CGPointEqualToPoint(_canonicalContentTranslation, translation) &&
687
+ _canonicalContentScale == scale) {
501
688
  return;
502
689
  }
503
- _normalizedContentTranslation = translation;
504
- _normalizedContentScale = scale;
690
+ _canonicalContentTranslation = translation;
691
+ _canonicalContentScale = scale;
505
692
  _contentContainer.layer.affineTransform =
506
693
  SmoothClipContentTransform(scale, translation);
507
694
  }
508
695
 
509
696
  - (void)applyRequestedClip {
510
- SmoothNormalizedClipGeometry geometry;
511
- if (![self normalizedRequestedGeometry:&geometry]) return;
512
-
513
- const BOOL visibilityChanged = _clipHidden != CGRectIsEmpty(geometry.rect);
514
- if (CGRectEqualToRect(_normalizedClip, geometry.rect) &&
515
- SmoothClipCornerRadiiEqual(_normalizedRadii, geometry.radii) &&
516
- _normalizedCurve == geometry.curve &&
517
- _normalizedHasExplicitRadii == _requestedHasExplicitRadii &&
697
+ SmoothClipCanonicalGeometry geometry;
698
+ if (![self canonicalRequestedGeometry:&geometry]) return;
699
+
700
+ const BOOL accessibilityHidden =
701
+ !SmoothClipRectIntersectsHost(geometry.rect, self.bounds);
702
+ const BOOL visibilityChanged =
703
+ _clipHidden != CGRectIsEmpty(geometry.rect) ||
704
+ _clipContainer.accessibilityElementsHidden != accessibilityHidden;
705
+ if (CGRectEqualToRect(_canonicalClip, geometry.rect) &&
706
+ SmoothClipCornerRadiiEqual(_canonicalRadii, geometry.radii) &&
707
+ _canonicalCurve == geometry.curve &&
708
+ _canonicalHasExplicitRadii == _requestedHasExplicitRadii &&
518
709
  CGPointEqualToPoint(
519
- _normalizedContentTranslation, _requestedContentTranslation) &&
520
- _normalizedContentScale == _requestedContentScale &&
710
+ _canonicalContentTranslation, _requestedContentTranslation) &&
711
+ _canonicalContentScale == _requestedContentScale &&
712
+ SmoothBoxShadowEqual(_canonicalShadow, _requestedShadow) &&
521
713
  !visibilityChanged) {
522
714
  return;
523
715
  }
@@ -525,6 +717,7 @@ static std::array<double, 11> SmoothClipVelocityChannels(
525
717
  hasExplicitRadii:_requestedHasExplicitRadii];
526
718
  [self writeContentTranslation:_requestedContentTranslation
527
719
  scale:_requestedContentScale];
720
+ [self writeShadow:_requestedShadow geometry:geometry];
528
721
  [self syncVisibilityForRect:geometry.rect];
529
722
  }
530
723
 
@@ -545,17 +738,44 @@ static std::array<double, 11> SmoothClipVelocityChannels(
545
738
 
546
739
  - (CGFloat)inheritedVelocityToRect:(CGRect)target
547
740
  radii:(SmoothClipCornerRadii)targetRadii
548
- contentTranslation:(CGPoint)targetContentTranslation {
741
+ contentTranslation:(CGPoint)targetContentTranslation
742
+ contentScale:(CGFloat)targetContentScale {
549
743
  return smoothclip::inheritedVelocity(
550
744
  _velocitySamples,
551
745
  SmoothClipVelocityChannels(
552
746
  target,
553
747
  targetRadii,
554
748
  targetContentTranslation,
555
- _requestedContentScale),
749
+ targetContentScale),
556
750
  CACurrentMediaTime());
557
751
  }
558
752
 
753
+ - (double)smoothClipInheritedVelocityToPresentation:
754
+ (smoothclip::Presentation)presentation {
755
+ SmoothClipCanonicalGeometry target;
756
+ const SmoothClipCornerRadii radii =
757
+ SmoothClipPresentationRadii(presentation.clip);
758
+ if (!SmoothClipCanonicalizeGeometry(
759
+ presentation.clip.x,
760
+ presentation.clip.y,
761
+ presentation.clip.width,
762
+ presentation.clip.height,
763
+ radii.topLeft,
764
+ radii.topRight,
765
+ radii.bottomRight,
766
+ radii.bottomLeft,
767
+ SmoothClipPresentationCurve(presentation.clip),
768
+ &target)) {
769
+ return 0;
770
+ }
771
+ return [self inheritedVelocityToRect:target.rect
772
+ radii:target.radii
773
+ contentTranslation:CGPointMake(
774
+ presentation.contentTranslateX,
775
+ presentation.contentTranslateY)
776
+ contentScale:presentation.contentScale];
777
+ }
778
+
559
779
  - (void)smoothClipApplyPresentation:(smoothclip::Presentation)presentation {
560
780
  [self smoothClipApplyPresentation:presentation recordVelocitySample:YES];
561
781
  }
@@ -565,21 +785,21 @@ static std::array<double, 11> SmoothClipVelocityChannels(
565
785
  [self stopLayerAnimationWithoutCallback];
566
786
  [self storeRequestedPresentation:presentation];
567
787
  [self applyRequestedClip];
568
- // Skipped for writes that are not interactive motion (a latched
788
+ // Skipped for writes that are not interactive motion (a pending-run
569
789
  // cancel-to-target): they must not enter the 'inherit' history.
570
790
  if (recordVelocitySample && _hasLayout) {
571
- [self recordInteractiveRect:_normalizedClip
572
- radii:_normalizedRadii
573
- contentTranslation:_normalizedContentTranslation
574
- contentScale:_normalizedContentScale];
791
+ [self recordInteractiveRect:_canonicalClip
792
+ radii:_canonicalRadii
793
+ contentTranslation:_canonicalContentTranslation
794
+ contentScale:_canonicalContentScale];
575
795
  }
576
796
  }
577
797
 
578
798
  - (smoothclip::Presentation)smoothClipCurrentPresentation {
579
- SmoothClipCornerRadii visibleRadii = _normalizedRadii;
799
+ SmoothClipCornerRadii visibleRadii = _canonicalRadii;
580
800
  const CGRect visibleRect = _activeAnimationId != 0
581
801
  ? [self presentationRectWithRadii:&visibleRadii]
582
- : _normalizedClip;
802
+ : _canonicalClip;
583
803
  CALayer *contentLayer = _activeAnimationId != 0
584
804
  ? (CALayer *)_contentContainer.layer.presentationLayer
585
805
  : _contentContainer.layer;
@@ -592,17 +812,45 @@ static std::array<double, 11> SmoothClipVelocityChannels(
592
812
  CGRectGetWidth(visibleRect),
593
813
  CGRectGetHeight(visibleRect),
594
814
  uniform ? visibleRadii.topLeft : 0};
595
- if (_normalizedHasExplicitRadii || !uniform) {
815
+ if (_canonicalHasExplicitRadii || !uniform) {
596
816
  geometry.topLeftRadius = visibleRadii.topLeft;
597
817
  geometry.topRightRadius = visibleRadii.topRight;
598
818
  geometry.bottomRightRadius = visibleRadii.bottomRight;
599
819
  geometry.bottomLeftRadius = visibleRadii.bottomLeft;
600
820
  }
601
- geometry.curve = _normalizedCurve == SmoothClipCornerCurveContinuous
821
+ geometry.curve = _canonicalCurve == SmoothClipCornerCurveContinuous
602
822
  ? smoothclip::ClipCurve::Continuous
603
823
  : smoothclip::ClipCurve::Circular;
604
824
  const CGFloat contentScale = hypot(transform.a, transform.b);
605
- return {geometry, transform.tx, transform.ty, contentScale};
825
+ smoothclip::Shadow shadow = _canonicalShadow;
826
+ CALayer *shadowLayer = _activeAnimationId != 0 && _shadowLayer != nil
827
+ ? (CALayer *)_shadowLayer.presentationLayer
828
+ : _shadowLayer;
829
+ if (shadowLayer == nil) return SmoothClipCanonicalSnapshot({
830
+ geometry, transform.tx, transform.ty, contentScale, shadow});
831
+ shadow.enabled = _canonicalShadow.enabled || shadowLayer.shadowOpacity > 0;
832
+ shadow.blurRadius = shadowLayer.shadowRadius * 2.0;
833
+ shadow.offsetX = shadowLayer.shadowOffset.width;
834
+ shadow.offsetY = shadowLayer.shadowOffset.height;
835
+ CGColorRef color = shadowLayer.shadowColor;
836
+ if (color != nil) {
837
+ UIColor *uiColor = [UIColor colorWithCGColor:color];
838
+ CGFloat red = 0, green = 0, blue = 0, alpha = 0;
839
+ if ([uiColor getRed:&red green:&green blue:&blue alpha:&alpha]) {
840
+ shadow.red = red;
841
+ shadow.green = green;
842
+ shadow.blue = blue;
843
+ shadow.alpha = alpha;
844
+ }
845
+ }
846
+ CGPathRef shadowPath = shadowLayer.shadowPath;
847
+ if (shadowPath != nil && !CGRectIsEmpty(visibleRect)) {
848
+ const CGRect shadowBounds = CGPathGetBoundingBox(shadowPath);
849
+ shadow.spreadDistance =
850
+ CGRectGetMinX(visibleRect) - CGRectGetMinX(shadowBounds);
851
+ }
852
+ return SmoothClipCanonicalSnapshot(
853
+ {geometry, transform.tx, transform.ty, contentScale, shadow});
606
854
  }
607
855
 
608
856
  - (BOOL)smoothClipIsJoinable {
@@ -615,28 +863,15 @@ static std::array<double, 11> SmoothClipVelocityChannels(
615
863
  - (BOOL)smoothClipCanDisplay {
616
864
  // A CA animation committed while this view's layer tree is detached from
617
865
  // the render tree does not survive the later attach commit; installs and
618
- // latch starts must wait until a frame can actually be produced.
866
+ // pre-ready runs must wait until a frame can actually be produced.
619
867
  return [self smoothClipIsJoinable] && self.window != nil &&
620
868
  smoothclip::applicationIsActive();
621
869
  }
622
870
 
623
- - (BOOL)smoothClipHasPendingInstall {
624
- return _pendingAnimationInstall;
625
- }
626
-
627
871
  - (void)smoothClipClearVelocitySamples {
628
872
  smoothclip::clearVelocitySamples(_velocitySamples);
629
873
  }
630
874
 
631
- - (double)smoothClipSpringContinuationVelocity {
632
- if (_activeAnimationId == 0 || _activeAnimationKind != 2 ||
633
- _pendingAnimationInstall) {
634
- return 0;
635
- }
636
- const double elapsed = MAX(0, CACurrentMediaTime() - _animationStartedAt);
637
- return smoothclip::springContinuationVelocity(_springAnimation, elapsed);
638
- }
639
-
640
875
  - (smoothclip::Presentation)smoothClipFreezePresentation {
641
876
  const smoothclip::Presentation visible =
642
877
  [self smoothClipCurrentPresentation];
@@ -662,7 +897,7 @@ static std::array<double, 11> SmoothClipVelocityChannels(
662
897
  (CAShapeLayer *)_unequalCornerMask.presentationLayer;
663
898
  if (mask == nil) mask = _unequalCornerMask;
664
899
  *radii = SmoothClipRadiiFromFixedPath(
665
- mask.path, rect, _normalizedRadii);
900
+ mask.path, rect, _canonicalRadii);
666
901
  } else {
667
902
  *radii = {
668
903
  layer.cornerRadius,
@@ -676,7 +911,6 @@ static std::array<double, 11> SmoothClipVelocityChannels(
676
911
  }
677
912
 
678
913
  - (void)stopLayerAnimationWithoutCallback {
679
- _pendingAnimationInstall = NO;
680
914
  if (_activeAnimationId == 0) return;
681
915
  // Invalidate before removing: CA may deliver didStop for the removed
682
916
  // animation after this method returns, when _ignoreAnimationCallback has
@@ -687,7 +921,8 @@ static std::array<double, 11> SmoothClipVelocityChannels(
687
921
  [_clipContainer.layer removeAnimationForKey:@"smoothClip.geometry"];
688
922
  [_contentContainer.layer removeAnimationForKey:@"smoothClip.content"];
689
923
  [_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
690
- [self applyStaticCornerRepresentation:[self normalizedGeometryValue]];
924
+ [_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
925
+ [self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
691
926
  _ignoreAnimationCallback = NO;
692
927
  _activeAnimationId = 0;
693
928
  _activeAnimationKind = 0;
@@ -706,15 +941,19 @@ static std::array<double, 11> SmoothClipVelocityChannels(
706
941
  }
707
942
 
708
943
  - (CFTimeInterval)springSettlingDurationWithVelocity:(double)velocity {
709
- // Every key path in the group shares mass/stiffness/damping and the same
710
- // normalized initialVelocity, so one settling solve covers all of them.
711
- CASpringAnimation *probe =
712
- [CASpringAnimation animationWithKeyPath:@"bounds.origin.x"];
713
- probe.mass = _springAnimation.mass;
714
- probe.stiffness = _springAnimation.stiffness;
715
- probe.damping = _springAnimation.damping;
716
- probe.initialVelocity = velocity;
717
- return probe.settlingDuration;
944
+ // Every key path shares one normalized trajectory. Derive the terminal
945
+ // duration from the same relative-energy rule as Android/Reanimated instead
946
+ // of asking each Core Animation property for an independent settle time.
947
+ smoothclip::ScalarSpringState state{0, velocity};
948
+ constexpr double step = 1.0 / 120.0;
949
+ double elapsed = 0;
950
+ while (elapsed < 10.0 &&
951
+ smoothclip::relativeSpringEnergy(state, _springAnimation) >
952
+ _springAnimation.energyThreshold) {
953
+ state = smoothclip::advanceScalarSpring(state, _springAnimation, step);
954
+ elapsed += step;
955
+ }
956
+ return elapsed;
718
957
  }
719
958
 
720
959
  - (CASpringAnimation *)springAnimationForKeyPath:(NSString *)keyPath
@@ -735,14 +974,16 @@ static std::array<double, 11> SmoothClipVelocityChannels(
735
974
  }
736
975
 
737
976
  - (void)installAnimationFromGeometry:
738
- (SmoothNormalizedClipGeometry)fromGeometry
977
+ (SmoothClipCanonicalGeometry)fromGeometry
739
978
  fromContentTranslation:(CGPoint)fromContentTranslation
740
979
  fromContentScale:(CGFloat)fromContentScale
980
+ fromShadow:(smoothclip::Shadow)fromShadow
741
981
  toGeometry:
742
- (SmoothNormalizedClipGeometry)toGeometry
982
+ (SmoothClipCanonicalGeometry)toGeometry
743
983
  hasExplicitToRadii:(BOOL)hasExplicitToRadii
744
984
  toContentTranslation:(CGPoint)toContentTranslation
745
985
  toContentScale:(CGFloat)toContentScale
986
+ toShadow:(smoothclip::Shadow)toShadow
746
987
  duration:(CFTimeInterval)duration
747
988
  sharedBeginTime:(CFTimeInterval)sharedBeginTime {
748
989
  const int32_t animationId = _activeAnimationId;
@@ -756,6 +997,9 @@ static std::array<double, 11> SmoothClipVelocityChannels(
756
997
  !CGRectIsEmpty(fromGeometry.rect)) {
757
998
  [self setClipContainerHidden:NO];
758
999
  }
1000
+ // Autonomous native motion has no per-frame accessibility layout updates.
1001
+ // Hide moving descendants until the endpoint is committed.
1002
+ _clipContainer.accessibilityElementsHidden = YES;
759
1003
 
760
1004
  CALayer *layer = _clipContainer.layer;
761
1005
  const CGRect toBounds = toGeometry.rect;
@@ -774,6 +1018,14 @@ static std::array<double, 11> SmoothClipVelocityChannels(
774
1018
  [self writeLayerGeometry:toGeometry
775
1019
  hasExplicitRadii:hasExplicitToRadii];
776
1020
  [self writeContentTranslation:toContentTranslation scale:toContentScale];
1021
+ _canonicalShadow = toShadow;
1022
+ SmoothBoxShadowNormalizeAnimationEndpoints(
1023
+ fromGeometry, &fromShadow, toGeometry, &toShadow);
1024
+ // The target model layer is complete before any presentation animations are
1025
+ // attached. Core Animation may remove the terminal presentation animation
1026
+ // before its delegate runs, so correctness cannot depend on completion-time
1027
+ // cleanup hiding a stale path or opaque color.
1028
+ [self writeShadowModel:toShadow geometry:toGeometry];
777
1029
  if (usesMask) {
778
1030
  // Keep one mask representation for the full interval, including an
779
1031
  // unequal→uniform transition. Switching layer.mask mid-animation would
@@ -791,10 +1043,41 @@ static std::array<double, 11> SmoothClipVelocityChannels(
791
1043
  ? SmoothClipCreateRoundedRectPath(
792
1044
  toGeometry.rect, toGeometry.radii, toGeometry.curve)
793
1045
  : nil;
794
-
1046
+ const BOOL fromShadowVisible =
1047
+ SmoothBoxShadowVisible(fromGeometry, fromShadow);
1048
+ const BOOL toShadowVisible =
1049
+ SmoothBoxShadowVisible(toGeometry, toShadow);
1050
+ const BOOL hasVisibleShadow = fromShadowVisible || toShadowVisible;
1051
+ const BOOL animatesShadowPath = hasVisibleShadow &&
1052
+ !SmoothBoxShadowPathInputEqual(
1053
+ fromGeometry, fromShadow, toGeometry, toShadow);
1054
+ const BOOL animatesShadowColor = hasVisibleShadow &&
1055
+ !SmoothBoxShadowColorEqual(fromShadow, toShadow);
1056
+ const BOOL animatesShadowRadius = hasVisibleShadow &&
1057
+ fromShadow.blurRadius != toShadow.blurRadius;
1058
+ const BOOL animatesShadowOffset = hasVisibleShadow &&
1059
+ (fromShadow.offsetX != toShadow.offsetX ||
1060
+ fromShadow.offsetY != toShadow.offsetY);
1061
+ const BOOL animatesShadow = animatesShadowPath || animatesShadowColor ||
1062
+ animatesShadowRadius || animatesShadowOffset;
1063
+ if (hasVisibleShadow) {
1064
+ [self ensureShadowLayer];
1065
+ // Reveal opacity is encoded in shadowColor alpha. Animating layer opacity
1066
+ // too would multiply both curves and produce a quadratic-looking fade.
1067
+ _shadowLayer.shadowOpacity = 1;
1068
+ }
1069
+ CGPathRef fromShadowPath = animatesShadowPath
1070
+ ? SmoothClipCreateShadowPath(fromGeometry, fromShadow)
1071
+ : nil;
1072
+ CGPathRef toShadowPath = animatesShadowPath
1073
+ ? SmoothClipCreateShadowPath(toGeometry, toShadow)
1074
+ : nil;
795
1075
  CAAnimationGroup *group = [CAAnimationGroup animation];
796
1076
  CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
797
- CAAnimation *maskAnimation = nil;
1077
+ CAAnimationGroup *shadowGroup = animatesShadow
1078
+ ? [CAAnimationGroup animation]
1079
+ : nil;
1080
+ CAAnimationGroup *maskAnimation = nil;
798
1081
  if (_activeAnimationKind == 1) {
799
1082
  CAMediaTimingFunction *timing = [CAMediaTimingFunction
800
1083
  functionWithControlPoints:_timingAnimation.controlPoint1X
@@ -812,10 +1095,21 @@ static std::array<double, 11> SmoothClipVelocityChannels(
812
1095
  timingFunction:timing],
813
1096
  ]];
814
1097
  if (usesMask) {
815
- maskAnimation = [self basicAnimationForKeyPath:@"path"
816
- fromValue:(__bridge id)fromMaskPath
817
- toValue:(__bridge id)toMaskPath
818
- timingFunction:timing];
1098
+ maskAnimation = [CAAnimationGroup animation];
1099
+ maskAnimation.animations = @[
1100
+ [self basicAnimationForKeyPath:@"path"
1101
+ fromValue:(__bridge id)fromMaskPath
1102
+ toValue:(__bridge id)toMaskPath
1103
+ timingFunction:timing],
1104
+ [self basicAnimationForKeyPath:@"bounds"
1105
+ fromValue:[NSValue valueWithCGRect:fromGeometry.rect]
1106
+ toValue:[NSValue valueWithCGRect:toGeometry.rect]
1107
+ timingFunction:timing],
1108
+ [self basicAnimationForKeyPath:@"position"
1109
+ fromValue:[NSValue valueWithCGPoint:fromPosition]
1110
+ toValue:[NSValue valueWithCGPoint:toPosition]
1111
+ timingFunction:timing],
1112
+ ];
819
1113
  } else {
820
1114
  [geometryAnimations addObject:
821
1115
  [self basicAnimationForKeyPath:@"cornerRadius"
@@ -844,6 +1138,33 @@ static std::array<double, 11> SmoothClipVelocityChannels(
844
1138
  }
845
1139
  contentGroup.animations = contentAnimations;
846
1140
  contentGroup.duration = duration;
1141
+ if (animatesShadow) {
1142
+ NSMutableArray<CAAnimation *> *shadowAnimations = [NSMutableArray array];
1143
+ if (animatesShadowPath) [shadowAnimations addObject:
1144
+ [self basicAnimationForKeyPath:@"shadowPath"
1145
+ fromValue:(__bridge id)fromShadowPath
1146
+ toValue:(__bridge id)toShadowPath
1147
+ timingFunction:timing]];
1148
+ if (animatesShadowColor) [shadowAnimations addObject:
1149
+ [self basicAnimationForKeyPath:@"shadowColor"
1150
+ fromValue:(__bridge id)[self colorForShadow:fromShadow]
1151
+ toValue:(__bridge id)[self colorForShadow:toShadow]
1152
+ timingFunction:timing]];
1153
+ if (animatesShadowRadius) [shadowAnimations addObject:
1154
+ [self basicAnimationForKeyPath:@"shadowRadius"
1155
+ fromValue:@(MAX(0, fromShadow.blurRadius) / 2.0)
1156
+ toValue:@(MAX(0, toShadow.blurRadius) / 2.0)
1157
+ timingFunction:timing]];
1158
+ if (animatesShadowOffset) [shadowAnimations addObject:
1159
+ [self basicAnimationForKeyPath:@"shadowOffset"
1160
+ fromValue:[NSValue valueWithCGSize:CGSizeMake(
1161
+ fromShadow.offsetX, fromShadow.offsetY)]
1162
+ toValue:[NSValue valueWithCGSize:CGSizeMake(
1163
+ toShadow.offsetX, toShadow.offsetY)]
1164
+ timingFunction:timing]];
1165
+ shadowGroup.animations = shadowAnimations;
1166
+ shadowGroup.duration = duration;
1167
+ }
847
1168
  } else {
848
1169
  // `_springAnimation.initialVelocity` is the interactive motion projected
849
1170
  // onto the current-to-target trajectory, normalized by that distance
@@ -888,11 +1209,44 @@ static std::array<double, 11> SmoothClipVelocityChannels(
888
1209
  duration:springDuration],
889
1210
  ]];
890
1211
  if (usesMask) {
891
- maskAnimation = [self springAnimationForKeyPath:@"path"
892
- fromValue:(__bridge id)fromMaskPath
893
- toValue:(__bridge id)toMaskPath
894
- velocity:velocity
895
- duration:springDuration];
1212
+ maskAnimation = [CAAnimationGroup animation];
1213
+ maskAnimation.animations = @[
1214
+ [self springAnimationForKeyPath:@"path"
1215
+ fromValue:(__bridge id)fromMaskPath
1216
+ toValue:(__bridge id)toMaskPath
1217
+ velocity:velocity
1218
+ duration:springDuration],
1219
+ [self springAnimationForKeyPath:@"bounds.origin.x"
1220
+ fromValue:@(fromGeometry.rect.origin.x)
1221
+ toValue:@(toGeometry.rect.origin.x)
1222
+ velocity:velocity
1223
+ duration:springDuration],
1224
+ [self springAnimationForKeyPath:@"bounds.origin.y"
1225
+ fromValue:@(fromGeometry.rect.origin.y)
1226
+ toValue:@(toGeometry.rect.origin.y)
1227
+ velocity:velocity
1228
+ duration:springDuration],
1229
+ [self springAnimationForKeyPath:@"bounds.size.width"
1230
+ fromValue:@(fromGeometry.rect.size.width)
1231
+ toValue:@(toGeometry.rect.size.width)
1232
+ velocity:velocity
1233
+ duration:springDuration],
1234
+ [self springAnimationForKeyPath:@"bounds.size.height"
1235
+ fromValue:@(fromGeometry.rect.size.height)
1236
+ toValue:@(toGeometry.rect.size.height)
1237
+ velocity:velocity
1238
+ duration:springDuration],
1239
+ [self springAnimationForKeyPath:@"position.x"
1240
+ fromValue:@(fromPosition.x)
1241
+ toValue:@(toPosition.x)
1242
+ velocity:velocity
1243
+ duration:springDuration],
1244
+ [self springAnimationForKeyPath:@"position.y"
1245
+ fromValue:@(fromPosition.y)
1246
+ toValue:@(toPosition.y)
1247
+ velocity:velocity
1248
+ duration:springDuration],
1249
+ ];
896
1250
  } else {
897
1251
  [geometryAnimations addObject:
898
1252
  [self springAnimationForKeyPath:@"cornerRadius"
@@ -902,9 +1256,8 @@ static std::array<double, 11> SmoothClipVelocityChannels(
902
1256
  duration:springDuration]];
903
1257
  }
904
1258
  group.animations = geometryAnimations;
905
- // A rebuilt spring (layout change, foreground resume) must run its own
906
- // settling time; clamping to the interrupted run's remaining wall-clock
907
- // time would truncate the oscillation and snap to the target.
1259
+ // Springs use their analytic settling time; a wall-clock duration would
1260
+ // truncate oscillation and snap to the target.
908
1261
  group.duration = springDuration;
909
1262
  NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
910
1263
  [self springAnimationForKeyPath:@"transform.translation.x"
@@ -928,6 +1281,37 @@ static std::array<double, 11> SmoothClipVelocityChannels(
928
1281
  }
929
1282
  contentGroup.animations = contentAnimations;
930
1283
  contentGroup.duration = springDuration;
1284
+ if (animatesShadow) {
1285
+ NSMutableArray<CAAnimation *> *shadowAnimations = [NSMutableArray array];
1286
+ if (animatesShadowPath) [shadowAnimations addObject:
1287
+ [self springAnimationForKeyPath:@"shadowPath"
1288
+ fromValue:(__bridge id)fromShadowPath
1289
+ toValue:(__bridge id)toShadowPath
1290
+ velocity:velocity
1291
+ duration:springDuration]];
1292
+ if (animatesShadowColor) [shadowAnimations addObject:
1293
+ [self springAnimationForKeyPath:@"shadowColor"
1294
+ fromValue:(__bridge id)[self colorForShadow:fromShadow]
1295
+ toValue:(__bridge id)[self colorForShadow:toShadow]
1296
+ velocity:velocity
1297
+ duration:springDuration]];
1298
+ if (animatesShadowRadius) [shadowAnimations addObject:
1299
+ [self springAnimationForKeyPath:@"shadowRadius"
1300
+ fromValue:@(MAX(0, fromShadow.blurRadius) / 2.0)
1301
+ toValue:@(MAX(0, toShadow.blurRadius) / 2.0)
1302
+ velocity:velocity
1303
+ duration:springDuration]];
1304
+ if (animatesShadowOffset) [shadowAnimations addObject:
1305
+ [self springAnimationForKeyPath:@"shadowOffset"
1306
+ fromValue:[NSValue valueWithCGSize:CGSizeMake(
1307
+ fromShadow.offsetX, fromShadow.offsetY)]
1308
+ toValue:[NSValue valueWithCGSize:CGSizeMake(
1309
+ toShadow.offsetX, toShadow.offsetY)]
1310
+ velocity:velocity
1311
+ duration:springDuration]];
1312
+ shadowGroup.animations = shadowAnimations;
1313
+ shadowGroup.duration = springDuration;
1314
+ }
931
1315
  }
932
1316
 
933
1317
  if (maskAnimation != nil) {
@@ -942,6 +1326,11 @@ static std::array<double, 11> SmoothClipVelocityChannels(
942
1326
  contentGroup.beginTime = [_contentContainer.layer
943
1327
  convertTime:sharedBeginTime
944
1328
  fromLayer:nil];
1329
+ if (animatesShadow) {
1330
+ shadowGroup.beginTime = [_shadowLayer
1331
+ convertTime:sharedBeginTime
1332
+ fromLayer:nil];
1333
+ }
945
1334
  if (maskAnimation != nil) {
946
1335
  maskAnimation.beginTime = [_unequalCornerMask
947
1336
  convertTime:sharedBeginTime
@@ -949,10 +1338,6 @@ static std::array<double, 11> SmoothClipVelocityChannels(
949
1338
  }
950
1339
  }
951
1340
 
952
- _animationDuration = group.duration;
953
- _animationStartedAt = sharedBeginTime > 0
954
- ? sharedBeginTime
955
- : CACurrentMediaTime();
956
1341
  _animationDelegate = [SmoothClipAnimationDelegate new];
957
1342
  _animationDelegate.view = self;
958
1343
  _animationDelegate.driverId = _driverId;
@@ -961,27 +1346,30 @@ static std::array<double, 11> SmoothClipVelocityChannels(
961
1346
  [layer addAnimation:group forKey:@"smoothClip.geometry"];
962
1347
  [_contentContainer.layer addAnimation:contentGroup
963
1348
  forKey:@"smoothClip.content"];
1349
+ if (animatesShadow) {
1350
+ [_shadowLayer addAnimation:shadowGroup forKey:@"smoothClip.shadow"];
1351
+ }
964
1352
  if (maskAnimation != nil) {
965
1353
  [_unequalCornerMask addAnimation:maskAnimation forKey:@"smoothClip.mask"];
966
1354
  }
967
1355
  if (fromMaskPath != nil) CGPathRelease(fromMaskPath);
968
1356
  if (toMaskPath != nil) CGPathRelease(toMaskPath);
969
- _animationInstallGeneration += 1;
1357
+ if (fromShadowPath != nil) CGPathRelease(fromShadowPath);
1358
+ if (toShadowPath != nil) CGPathRelease(toShadowPath);
970
1359
  }
971
1360
 
972
1361
  - (BOOL)startAnimationToRequestedGeometryWithDuration:(CFTimeInterval)duration
973
1362
  sharedBeginTime:
974
1363
  (CFTimeInterval)sharedBeginTime {
975
1364
  if (![self smoothClipCanDisplay]) {
976
- _pendingAnimationInstall = YES;
977
1365
  return NO;
978
1366
  }
979
- SmoothNormalizedClipGeometry target;
980
- if (![self normalizedRequestedGeometry:&target]) return NO;
1367
+ SmoothClipCanonicalGeometry target;
1368
+ if (![self canonicalRequestedGeometry:&target]) return NO;
981
1369
  const smoothclip::Presentation current = [self smoothClipCurrentPresentation];
982
1370
  const SmoothClipCornerRadii currentRadii =
983
1371
  SmoothClipPresentationRadii(current.clip);
984
- const SmoothNormalizedClipGeometry fromGeometry = {
1372
+ const SmoothClipCanonicalGeometry fromGeometry = {
985
1373
  .rect = CGRectMake(
986
1374
  current.clip.x,
987
1375
  current.clip.y,
@@ -998,10 +1386,12 @@ static std::array<double, 11> SmoothClipVelocityChannels(
998
1386
  current.contentTranslateX,
999
1387
  current.contentTranslateY)
1000
1388
  fromContentScale:current.contentScale
1389
+ fromShadow:current.shadow
1001
1390
  toGeometry:target
1002
1391
  hasExplicitToRadii:_requestedHasExplicitRadii
1003
1392
  toContentTranslation:_requestedContentTranslation
1004
1393
  toContentScale:_requestedContentScale
1394
+ toShadow:_requestedShadow
1005
1395
  duration:duration
1006
1396
  sharedBeginTime:sharedBeginTime];
1007
1397
  return YES;
@@ -1045,12 +1435,13 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1045
1435
  [self storeRequestedPresentation:presentation];
1046
1436
  _springAnimation = animation;
1047
1437
  if (animation.inheritVelocity) {
1048
- SmoothNormalizedClipGeometry target;
1049
- if ([self normalizedRequestedGeometry:&target]) {
1438
+ SmoothClipCanonicalGeometry target;
1439
+ if ([self canonicalRequestedGeometry:&target]) {
1050
1440
  _springAnimation.initialVelocity =
1051
1441
  [self inheritedVelocityToRect:target.rect
1052
1442
  radii:target.radii
1053
- contentTranslation:_requestedContentTranslation];
1443
+ contentTranslation:_requestedContentTranslation
1444
+ contentScale:_requestedContentScale];
1054
1445
  } else {
1055
1446
  _springAnimation.initialVelocity = 0;
1056
1447
  }
@@ -1061,201 +1452,6 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1061
1452
  sharedBeginTime:sharedBeginTime];
1062
1453
  }
1063
1454
 
1064
- - (CAKeyframeAnimation *)keyframeAnimationForKeyPath:(NSString *)keyPath
1065
- values:(NSArray *)values
1066
- keyTimes:(NSArray<NSNumber *> *)keyTimes {
1067
- CAKeyframeAnimation *animation =
1068
- [CAKeyframeAnimation animationWithKeyPath:keyPath];
1069
- animation.values = values;
1070
- animation.keyTimes = keyTimes;
1071
- animation.calculationMode = kCAAnimationLinear;
1072
- return animation;
1073
- }
1074
-
1075
- - (BOOL)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
1076
- keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
1077
- durationMs:(double)durationMs
1078
- animationId:(int32_t)animationId {
1079
- return [self smoothClipAnimateKeyframes:presentation
1080
- keyframes:keyframes
1081
- durationMs:durationMs
1082
- animationId:animationId
1083
- sharedBeginTime:0];
1084
- }
1085
-
1086
- - (BOOL)smoothClipAnimateKeyframes:(smoothclip::Presentation)presentation
1087
- keyframes:(const std::vector<smoothclip::Keyframe> &)keyframes
1088
- durationMs:(double)durationMs
1089
- animationId:(int32_t)animationId
1090
- sharedBeginTime:(CFTimeInterval)sharedBeginTime {
1091
- if (keyframes.size() < 2) return NO;
1092
- [self storeRequestedPresentation:presentation];
1093
- _activeAnimationKind = 3;
1094
- _activeAnimationId = animationId;
1095
- if (![self smoothClipCanDisplay]) {
1096
- _pendingAnimationInstall = YES;
1097
- return NO;
1098
- }
1099
- [self stopLayerAnimationWithoutCallback];
1100
- _activeAnimationKind = 3;
1101
- _activeAnimationId = animationId;
1102
-
1103
- const NSUInteger frameCount = keyframes.size();
1104
- const CGSize hostSize = self.bounds.size;
1105
- NSMutableArray<NSNumber *> *times =
1106
- [NSMutableArray arrayWithCapacity:frameCount];
1107
- NSMutableArray *boundsValues = [NSMutableArray arrayWithCapacity:frameCount];
1108
- NSMutableArray *positionValues =
1109
- [NSMutableArray arrayWithCapacity:frameCount];
1110
- NSMutableArray *radiusValues = [NSMutableArray arrayWithCapacity:frameCount];
1111
- NSMutableArray *maskPathValues =
1112
- [NSMutableArray arrayWithCapacity:frameCount];
1113
- NSMutableArray *translateXValues =
1114
- [NSMutableArray arrayWithCapacity:frameCount];
1115
- NSMutableArray *translateYValues =
1116
- [NSMutableArray arrayWithCapacity:frameCount];
1117
- NSMutableArray *scaleValues =
1118
- [NSMutableArray arrayWithCapacity:frameCount];
1119
- BOOL usesMask = NO;
1120
- BOOL animatesScale = NO;
1121
- SmoothClipCornerCurve firstCurve = SmoothClipCornerCurveCircular;
1122
- BOOL hasFirstCurve = NO;
1123
- std::vector<SmoothNormalizedClipGeometry> normalizedFrames;
1124
- normalizedFrames.reserve(frameCount);
1125
- for (const smoothclip::Keyframe &frame : keyframes) {
1126
- const SmoothClipCornerRadii frameRadii =
1127
- SmoothClipPresentationRadii(frame.presentation.clip);
1128
- const SmoothClipCornerCurve frameCurve =
1129
- SmoothClipPresentationCurve(frame.presentation.clip);
1130
- SmoothNormalizedClipGeometry normalized;
1131
- if (!SmoothClipNormalizeGeometryV2(
1132
- frame.presentation.clip.x,
1133
- frame.presentation.clip.y,
1134
- frame.presentation.clip.width,
1135
- frame.presentation.clip.height,
1136
- frameRadii.topLeft,
1137
- frameRadii.topRight,
1138
- frameRadii.bottomRight,
1139
- frameRadii.bottomLeft,
1140
- frameCurve,
1141
- hostSize,
1142
- &normalized)) return NO;
1143
- normalizedFrames.push_back(normalized);
1144
- if (!SmoothClipCornerRadiiAreUniform(normalized.radii)) usesMask = YES;
1145
- if (!hasFirstCurve) {
1146
- firstCurve = normalized.curve;
1147
- hasFirstCurve = YES;
1148
- } else if (normalized.curve != firstCurve) {
1149
- usesMask = YES;
1150
- }
1151
- if (frame.presentation.contentScale != 1) animatesScale = YES;
1152
- [times addObject:@(frame.offset)];
1153
- [boundsValues addObject:[NSValue valueWithCGRect:normalized.rect]];
1154
- [positionValues addObject:[NSValue valueWithCGPoint:CGPointMake(
1155
- CGRectGetMidX(normalized.rect), CGRectGetMidY(normalized.rect))]];
1156
- [radiusValues addObject:@(normalized.radius)];
1157
- [translateXValues addObject:@(frame.presentation.contentTranslateX)];
1158
- [translateYValues addObject:@(frame.presentation.contentTranslateY)];
1159
- [scaleValues addObject:@(frame.presentation.contentScale)];
1160
- }
1161
-
1162
- if (usesMask) {
1163
- for (const SmoothNormalizedClipGeometry &normalized : normalizedFrames) {
1164
- CGPathRef path = SmoothClipCreateRoundedRectPath(
1165
- normalized.rect, normalized.radii, normalized.curve);
1166
- [maskPathValues addObject:(__bridge id)path];
1167
- CGPathRelease(path);
1168
- }
1169
- }
1170
-
1171
- SmoothNormalizedClipGeometry target;
1172
- if (![self normalizedRequestedGeometry:&target]) return NO;
1173
- [self writeLayerGeometry:target
1174
- hasExplicitRadii:_requestedHasExplicitRadii];
1175
- [self writeContentTranslation:_requestedContentTranslation
1176
- scale:_requestedContentScale];
1177
- if (usesMask) {
1178
- [self configureUnequalCornerMaskForGeometry:target];
1179
- _clipContainer.layer.cornerRadius = 0;
1180
- _clipContainer.layer.mask = _unequalCornerMask;
1181
- }
1182
- [self setClipContainerHidden:NO];
1183
-
1184
- CAAnimationGroup *group = [CAAnimationGroup animation];
1185
- NSMutableArray<CAAnimation *> *geometryAnimations = [NSMutableArray arrayWithArray:@[
1186
- [self keyframeAnimationForKeyPath:@"bounds"
1187
- values:boundsValues
1188
- keyTimes:times],
1189
- [self keyframeAnimationForKeyPath:@"position"
1190
- values:positionValues
1191
- keyTimes:times],
1192
- ]];
1193
- if (!usesMask) {
1194
- [geometryAnimations addObject:
1195
- [self keyframeAnimationForKeyPath:@"cornerRadius"
1196
- values:radiusValues
1197
- keyTimes:times]];
1198
- }
1199
- group.animations = geometryAnimations;
1200
- group.duration = MAX(0, durationMs) / 1000.0;
1201
- CAAnimationGroup *contentGroup = [CAAnimationGroup animation];
1202
- NSMutableArray<CAAnimation *> *contentAnimations = [NSMutableArray arrayWithArray:@[
1203
- [self keyframeAnimationForKeyPath:@"transform.translation.x"
1204
- values:translateXValues
1205
- keyTimes:times],
1206
- [self keyframeAnimationForKeyPath:@"transform.translation.y"
1207
- values:translateYValues
1208
- keyTimes:times],
1209
- ]];
1210
- if (animatesScale) {
1211
- [contentAnimations addObject:
1212
- [self keyframeAnimationForKeyPath:@"transform.scale"
1213
- values:scaleValues
1214
- keyTimes:times]];
1215
- }
1216
- contentGroup.animations = contentAnimations;
1217
- contentGroup.duration = group.duration;
1218
-
1219
- CAKeyframeAnimation *maskAnimation = usesMask
1220
- ? [self keyframeAnimationForKeyPath:@"path"
1221
- values:maskPathValues
1222
- keyTimes:times]
1223
- : nil;
1224
- maskAnimation.duration = group.duration;
1225
-
1226
- if (sharedBeginTime > 0) {
1227
- group.beginTime = [_clipContainer.layer
1228
- convertTime:sharedBeginTime
1229
- fromLayer:nil];
1230
- contentGroup.beginTime = [_contentContainer.layer
1231
- convertTime:sharedBeginTime
1232
- fromLayer:nil];
1233
- if (maskAnimation != nil) {
1234
- maskAnimation.beginTime = [_unequalCornerMask
1235
- convertTime:sharedBeginTime
1236
- fromLayer:nil];
1237
- }
1238
- }
1239
-
1240
- _animationDuration = group.duration;
1241
- _animationStartedAt = sharedBeginTime > 0
1242
- ? sharedBeginTime
1243
- : CACurrentMediaTime();
1244
- _animationDelegate = [SmoothClipAnimationDelegate new];
1245
- _animationDelegate.view = self;
1246
- _animationDelegate.driverId = _driverId;
1247
- _animationDelegate.animationId = _activeAnimationId;
1248
- group.delegate = _animationDelegate;
1249
- [_clipContainer.layer addAnimation:group forKey:@"smoothClip.geometry"];
1250
- [_contentContainer.layer addAnimation:contentGroup
1251
- forKey:@"smoothClip.content"];
1252
- if (maskAnimation != nil) {
1253
- [_unequalCornerMask addAnimation:maskAnimation forKey:@"smoothClip.mask"];
1254
- }
1255
- _animationInstallGeneration += 1;
1256
- return YES;
1257
- }
1258
-
1259
1455
  - (void)smoothClipCancelAnimationUsingTarget:(BOOL)useTarget {
1260
1456
  if (_activeAnimationId == 0) return;
1261
1457
  const smoothclip::Presentation visible =
@@ -1277,8 +1473,11 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1277
1473
  _activeAnimationKind = 0;
1278
1474
  _animationDelegate = nil;
1279
1475
  [_unequalCornerMask removeAnimationForKey:@"smoothClip.mask"];
1280
- [self applyStaticCornerRepresentation:[self normalizedGeometryValue]];
1281
- [self syncVisibilityForRect:_normalizedClip];
1476
+ [_shadowLayer removeAnimationForKey:@"smoothClip.shadow"];
1477
+ [self applyStaticCornerRepresentation:[self canonicalGeometryValue]];
1478
+ [self writeShadow:_requestedShadow
1479
+ geometry:[self canonicalGeometryValue]];
1480
+ [self syncVisibilityForRect:_canonicalClip];
1282
1481
  smoothclip::viewAnimationDidStop(
1283
1482
  driverId, animationId, self, finished);
1284
1483
  }
@@ -1288,45 +1487,38 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1288
1487
  const auto &newProps =
1289
1488
  *std::static_pointer_cast<const SmoothClipViewProps>(props);
1290
1489
  [super updateProps:props oldProps:oldProps];
1490
+ self.clipsToBounds = YES;
1291
1491
 
1292
1492
  const uint64_t nextDriverId =
1293
1493
  isfinite(newProps.driverId) && newProps.driverId > 0
1294
1494
  ? static_cast<uint64_t>(newProps.driverId)
1295
1495
  : 0;
1296
1496
  smoothclip::Presentation initial{};
1297
- if (newProps.presentationVersion == 2) {
1298
- if (!SmoothClipBuildPresentationV2(
1299
- newProps.initialClipX,
1300
- newProps.initialClipY,
1301
- newProps.initialClipWidth,
1302
- newProps.initialClipHeight,
1303
- newProps.initialClipTopLeftRadius,
1304
- newProps.initialClipTopRightRadius,
1305
- newProps.initialClipBottomRightRadius,
1306
- newProps.initialClipBottomLeftRadius,
1307
- newProps.initialClipCurve,
1308
- newProps.initialContentTranslateX,
1309
- newProps.initialContentTranslateY,
1310
- newProps.initialContentScale,
1311
- &initial)) {
1312
- // The initial V2 presentation is one atomic value. Do not unregister an
1313
- // old driver or apply a subset of valid channels when any field rejects.
1314
- return;
1315
- }
1316
- } else if (newProps.presentationVersion == 0 ||
1317
- newProps.presentationVersion == 1) {
1318
- // Version zero is what old JS produces when the versioned prop does not
1319
- // exist. Preserve that old-JS/new-native path byte-for-byte as V1.
1320
- initial = {
1321
- {newProps.initialClipX,
1322
- newProps.initialClipY,
1323
- newProps.initialClipWidth,
1324
- newProps.initialClipHeight,
1325
- newProps.initialClipRadius},
1326
- newProps.initialContentTranslateX,
1327
- newProps.initialContentTranslateY};
1328
- } else {
1329
- // A future protocol must not be silently interpreted as V1.
1497
+ if (!SmoothClipBuildPresentation(
1498
+ newProps.initialClipX,
1499
+ newProps.initialClipY,
1500
+ newProps.initialClipWidth,
1501
+ newProps.initialClipHeight,
1502
+ newProps.initialClipTopLeftRadius,
1503
+ newProps.initialClipTopRightRadius,
1504
+ newProps.initialClipBottomRightRadius,
1505
+ newProps.initialClipBottomLeftRadius,
1506
+ newProps.initialClipCurve,
1507
+ newProps.initialContentTranslateX,
1508
+ newProps.initialContentTranslateY,
1509
+ newProps.initialContentScale,
1510
+ newProps.initialClipBoxShadowEnabled,
1511
+ newProps.initialClipBoxShadowRed,
1512
+ newProps.initialClipBoxShadowGreen,
1513
+ newProps.initialClipBoxShadowBlue,
1514
+ newProps.initialClipBoxShadowAlpha,
1515
+ newProps.initialClipBoxShadowOffsetX,
1516
+ newProps.initialClipBoxShadowOffsetY,
1517
+ newProps.initialClipBoxShadowBlurRadius,
1518
+ newProps.initialClipBoxShadowSpreadDistance,
1519
+ &initial)) {
1520
+ // The initial presentation is atomic. Never apply a valid subset when one
1521
+ // field rejects.
1330
1522
  return;
1331
1523
  }
1332
1524
 
@@ -1354,121 +1546,24 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1354
1546
 
1355
1547
  - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
1356
1548
  oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
1357
- const BOOL wasAnimating = _activeAnimationId != 0;
1358
- const BOOL wasPending = _pendingAnimationInstall;
1359
- const uint32_t installGenerationAtEntry = _animationInstallGeneration;
1360
- const smoothclip::Presentation visible = wasAnimating && !wasPending
1361
- ? [self smoothClipCurrentPresentation]
1362
- : smoothclip::Presentation{{0, 0, 0, 0, 0}, 0, 0, 1};
1363
- const CGRect visibleRect = CGRectMake(
1364
- visible.clip.x,
1365
- visible.clip.y,
1366
- visible.clip.width,
1367
- visible.clip.height);
1368
- const SmoothClipCornerRadii visibleRadii =
1369
- SmoothClipPresentationRadii(visible.clip);
1370
- const SmoothClipCornerCurve visibleCurve =
1371
- SmoothClipPresentationCurve(visible.clip);
1372
- const CGPoint visibleContentTranslation = CGPointMake(
1373
- visible.contentTranslateX,
1374
- visible.contentTranslateY);
1375
- const CGFloat visibleContentScale = visible.contentScale;
1376
- CFTimeInterval remaining = wasAnimating
1377
- ? MAX(0, _animationDuration -
1378
- (CACurrentMediaTime() - _animationStartedAt))
1379
- : 0;
1380
- const double springContinuationVelocity =
1381
- wasAnimating && !wasPending && _activeAnimationKind == 2
1382
- ? [self smoothClipSpringContinuationVelocity]
1383
- : 0;
1384
-
1385
1549
  [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
1386
1550
  _hasLayout = YES;
1387
1551
  [self layoutContentContainer];
1388
1552
  if (_driverId != 0) {
1389
- // Both edges matter: zero size/detach/background suspend an installed
1390
- // participant; the first positive visible layout resumes the held latch.
1391
1553
  smoothclip::viewDisplayabilityChanged(_driverId, self);
1392
1554
  }
1393
- if (wasPending) {
1394
- // The registry either kept the install deferred or installed the rebased
1395
- // latch. Relayout must not independently restart a background-held id.
1396
- if (_activeAnimationId == 0) [self applyRequestedClip];
1397
- return;
1398
- }
1399
- if (wasAnimating) {
1400
- // A negative transition may have frozen this host and re-latched the
1401
- // registry above. Its non-recording freeze is already the correct model
1402
- // state for the new lifecycle; do not resurrect a local timer.
1403
- if (_activeAnimationId == 0 || ![self smoothClipCanDisplay]) return;
1404
- // The lifecycle notification above may itself have installed this
1405
- // animation from the registry's trimmed remainder (a latch resume inside
1406
- // this very layout pass). That install rebased the clocks; the `remaining`
1407
- // captured on entry predates the suspension and would rebuild a shorter —
1408
- // possibly zero-duration — run on top of the fresh one. The id alone
1409
- // cannot detect this (a resume keeps the animation id), so compare the
1410
- // install generation.
1411
- if (_animationInstallGeneration != installGenerationAtEntry) return;
1412
- const int32_t animationId = _activeAnimationId;
1413
- const NSInteger animationKind = _activeAnimationKind;
1414
- [self stopLayerAnimationWithoutCallback];
1415
- _activeAnimationId = animationId;
1416
- _activeAnimationKind = animationKind;
1417
- if (_activeAnimationKind == 1 && _animationDuration > 0) {
1418
- const double progress = smoothclip::clamp01(
1419
- 1 - remaining / _animationDuration);
1420
- _timingAnimation =
1421
- smoothclip::timingRemainder(_timingAnimation, progress).animation;
1422
- remaining = _timingAnimation.durationMs / 1000.0;
1423
- } else if (_activeAnimationKind == 2) {
1424
- _springAnimation.initialVelocity = springContinuationVelocity;
1425
- _springAnimation.inheritVelocity = false;
1426
- } else if (_activeAnimationKind == 3) {
1427
- _activeAnimationKind = 1;
1428
- _timingAnimation = {remaining * 1000.0, 0, 0, 1, 1, 2};
1429
- }
1430
- SmoothNormalizedClipGeometry current;
1431
- SmoothClipNormalizeGeometryV2(
1432
- CGRectGetMinX(visibleRect),
1433
- CGRectGetMinY(visibleRect),
1434
- CGRectGetWidth(visibleRect),
1435
- CGRectGetHeight(visibleRect),
1436
- visibleRadii.topLeft,
1437
- visibleRadii.topRight,
1438
- visibleRadii.bottomRight,
1439
- visibleRadii.bottomLeft,
1440
- visibleCurve,
1441
- self.bounds.size,
1442
- &current);
1443
- SmoothNormalizedClipGeometry target;
1444
- if ([self normalizedRequestedGeometry:&target]) {
1445
- [self installAnimationFromGeometry:current
1446
- fromContentTranslation:visibleContentTranslation
1447
- fromContentScale:visibleContentScale
1448
- toGeometry:target
1449
- hasExplicitToRadii:_requestedHasExplicitRadii
1450
- toContentTranslation:_requestedContentTranslation
1451
- toContentScale:_requestedContentScale
1452
- duration:remaining
1453
- sharedBeginTime:0];
1454
- }
1455
- } else {
1456
- if (_driverId != 0 && smoothclip::hasActiveAnimation(_driverId)) {
1457
- // Registry-level lifecycle pause owns the frozen model. A layout pass
1458
- // while its latch is held must not apply the requested target or start a
1459
- // per-view timer; foreground/reattach will normalize and resume it once.
1460
- return;
1461
- }
1555
+ if (_activeAnimationId == 0 &&
1556
+ (_driverId == 0 || !smoothclip::hasActiveAnimation(_driverId))) {
1462
1557
  [self applyRequestedClip];
1463
1558
  }
1464
1559
  }
1465
1560
 
1466
1561
  - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
1467
1562
  if (![super pointInside:point withEvent:event]) return NO;
1468
- SmoothClipCornerRadii radii = _normalizedRadii;
1563
+ SmoothClipCornerRadii radii = _canonicalRadii;
1469
1564
  const CGRect clip = _activeAnimationId != 0
1470
1565
  ? [self presentationRectWithRadii:&radii]
1471
- : _normalizedClip;
1566
+ : _canonicalClip;
1472
1567
  if (CGRectIsEmpty(clip) || !CGRectContainsPoint(clip, point)) return NO;
1473
1568
  CGPathRef path = nil;
1474
1569
  if (_clipContainer.layer.mask == _unequalCornerMask) {
@@ -1479,7 +1574,7 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1479
1574
  path = mask.path;
1480
1575
  return path != nil && CGPathContainsPoint(path, NULL, point, NO);
1481
1576
  }
1482
- path = SmoothClipCreateRoundedRectPath(clip, radii, _normalizedCurve);
1577
+ path = SmoothClipCreateRoundedRectPath(clip, radii, _canonicalCurve);
1483
1578
  const BOOL contains = CGPathContainsPoint(path, NULL, point, NO);
1484
1579
  CGPathRelease(path);
1485
1580
  return contains;
@@ -1491,31 +1586,7 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1491
1586
  RCTSmoothClipViewHandleCommand(self, commandName, args);
1492
1587
  }
1493
1588
 
1494
- - (void)setClipGeometry:(double)x
1495
- y:(double)y
1496
- width:(double)width
1497
- height:(double)height
1498
- radius:(double)radius {
1499
- _commandIsAuthoritative = YES;
1500
- [self smoothClipApplyPresentation:
1501
- {{x, y, width, height, radius}, 0, 0}];
1502
- }
1503
-
1504
1589
  - (void)setClipPresentation:(double)x
1505
- y:(double)y
1506
- width:(double)width
1507
- height:(double)height
1508
- radius:(double)radius
1509
- contentTranslateX:(double)contentTranslateX
1510
- contentTranslateY:(double)contentTranslateY {
1511
- _commandIsAuthoritative = YES;
1512
- [self smoothClipApplyPresentation:
1513
- {{x, y, width, height, radius},
1514
- contentTranslateX,
1515
- contentTranslateY}];
1516
- }
1517
-
1518
- - (void)setClipPresentationV2:(double)x
1519
1590
  y:(double)y
1520
1591
  width:(double)width
1521
1592
  height:(double)height
@@ -1526,9 +1597,18 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1526
1597
  curveCode:(NSInteger)curveCode
1527
1598
  contentTranslateX:(double)contentTranslateX
1528
1599
  contentTranslateY:(double)contentTranslateY
1529
- contentScale:(double)contentScale {
1600
+ contentScale:(double)contentScale
1601
+ shadowEnabled:(BOOL)shadowEnabled
1602
+ shadowRed:(double)shadowRed
1603
+ shadowGreen:(double)shadowGreen
1604
+ shadowBlue:(double)shadowBlue
1605
+ shadowAlpha:(double)shadowAlpha
1606
+ shadowOffsetX:(double)shadowOffsetX
1607
+ shadowOffsetY:(double)shadowOffsetY
1608
+ shadowBlurRadius:(double)shadowBlurRadius
1609
+ shadowSpreadDistance:(double)shadowSpreadDistance {
1530
1610
  smoothclip::Presentation presentation{};
1531
- if (!SmoothClipBuildPresentationV2(
1611
+ if (!SmoothClipBuildPresentation(
1532
1612
  x,
1533
1613
  y,
1534
1614
  width,
@@ -1541,6 +1621,15 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1541
1621
  contentTranslateX,
1542
1622
  contentTranslateY,
1543
1623
  contentScale,
1624
+ shadowEnabled,
1625
+ shadowRed,
1626
+ shadowGreen,
1627
+ shadowBlue,
1628
+ shadowAlpha,
1629
+ shadowOffsetX,
1630
+ shadowOffsetY,
1631
+ shadowBlurRadius,
1632
+ shadowSpreadDistance,
1544
1633
  &presentation)) {
1545
1634
  return;
1546
1635
  }
@@ -1554,23 +1643,24 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1554
1643
  _driverId = 0;
1555
1644
  }
1556
1645
  [self stopLayerAnimationWithoutCallback];
1557
- _pendingAnimationInstall = NO;
1558
1646
  smoothclip::clearVelocitySamples(_velocitySamples);
1559
1647
  [super prepareForRecycle];
1560
1648
  _requestedClip = CGRectZero;
1561
- _normalizedClip = CGRectZero;
1649
+ _canonicalClip = CGRectZero;
1562
1650
  _requestedRadius = 0;
1563
- _normalizedRadius = 0;
1651
+ _canonicalRadius = 0;
1564
1652
  _requestedRadii = {0, 0, 0, 0};
1565
- _normalizedRadii = {0, 0, 0, 0};
1653
+ _canonicalRadii = {0, 0, 0, 0};
1566
1654
  _requestedCurve = SmoothClipCornerCurveCircular;
1567
- _normalizedCurve = SmoothClipCornerCurveCircular;
1655
+ _canonicalCurve = SmoothClipCornerCurveCircular;
1568
1656
  _requestedHasExplicitRadii = NO;
1569
- _normalizedHasExplicitRadii = NO;
1657
+ _canonicalHasExplicitRadii = NO;
1570
1658
  _requestedContentTranslation = CGPointZero;
1571
- _normalizedContentTranslation = CGPointZero;
1659
+ _canonicalContentTranslation = CGPointZero;
1572
1660
  _requestedContentScale = 1;
1573
- _normalizedContentScale = 1;
1661
+ _canonicalContentScale = 1;
1662
+ _requestedShadow = {};
1663
+ _canonicalShadow = {};
1574
1664
  _hasLayout = NO;
1575
1665
  _commandIsAuthoritative = NO;
1576
1666
 
@@ -1582,6 +1672,10 @@ static std::array<double, 11> SmoothClipVelocityChannels(
1582
1672
  layer.mask = nil;
1583
1673
  _unequalCornerMask.path = nil;
1584
1674
  _contentContainer.layer.affineTransform = CGAffineTransformIdentity;
1675
+ _shadowLayer.shadowPath = nil;
1676
+ _shadowLayer.shadowOpacity = 0;
1677
+ _shadowLayer.shadowRadius = 0;
1678
+ _shadowLayer.shadowOffset = CGSizeZero;
1585
1679
  [self setClipContainerHidden:YES];
1586
1680
  }
1587
1681