@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.7

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 (236) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +7 -0
  3. package/android/CLAUDE.md +19 -10
  4. package/android/CORE.md +3 -2
  5. package/android/consumer-rules.pro +6 -0
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  8. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  12. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  14. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  15. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  16. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
  19. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  20. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  23. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  24. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  48. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  58. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  59. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  60. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  62. package/android/src/main/res/values/styles.xml +1 -1
  63. package/android/src/main/res/values-night/styles.xml +1 -1
  64. package/cli/doctor/checks/app.js +16 -1
  65. package/cli/project.js +4 -0
  66. package/cli/setup/index.js +5 -0
  67. package/cli/setup/steps/metro.js +46 -0
  68. package/docs-md/architecture/native-screens.md +4 -0
  69. package/docs-md/architecture/theme-and-context.md +131 -12
  70. package/docs-md/architecture/transitions.md +43 -4
  71. package/docs-md/flows/bottom-sheets.md +4 -2
  72. package/docs-md/fundamentals/screens-and-props.md +9 -1
  73. package/docs-md/guides/cli.md +3 -1
  74. package/docs-md/help/troubleshooting.md +1 -1
  75. package/docs-md/index.json +29 -1
  76. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  77. package/docs-md/screen-ui/bar-buttons.md +1 -1
  78. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  79. package/docs-md/start/installation.md +16 -1
  80. package/ios/Animations/ANNFrameDriver.h +55 -15
  81. package/ios/Animations/ANNFrameDriver.mm +214 -60
  82. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  83. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  84. package/ios/Animations/ANNTransitionPlan.h +2 -2
  85. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  86. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  87. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  88. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  89. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  90. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  91. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  92. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  93. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  94. package/ios/CLAUDE.md +15 -7
  95. package/ios/CORE.md +1 -1
  96. package/ios/Core/ANNAppDelegate.mm +0 -2
  97. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  98. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  99. package/ios/Core/ANNSceneDelegate.mm +2 -2
  100. package/ios/Core/ApexNavigation.mm +6 -2
  101. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  102. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  103. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  104. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  105. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  106. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  107. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  108. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  109. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  110. package/ios/Options/ANNAnimationOptions.h +2 -1
  111. package/ios/Options/ANNAnimationOptions.mm +1 -1
  112. package/ios/Options/ANNOptions.h +7 -0
  113. package/ios/Options/ANNOptions.mm +45 -0
  114. package/ios/Options/ANNOptionsReader.mm +2 -4
  115. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  116. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  117. package/ios/Options/Params/ColorParser.h +4 -3
  118. package/ios/Options/Params/ColorParser.mm +22 -4
  119. package/ios/React/ANNSurfaceHostingView.h +8 -0
  120. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  121. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  122. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  123. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  124. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  125. package/lib/module/ARCHITECTURE.md +9 -3
  126. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  127. package/lib/module/api/app/AppRegistry.js +14 -2
  128. package/lib/module/api/app/AppRegistry.js.map +1 -1
  129. package/lib/module/api/app/createNavigation.js +188 -66
  130. package/lib/module/api/app/createNavigation.js.map +1 -1
  131. package/lib/module/api/events/navigationEvents.js +6 -2
  132. package/lib/module/api/events/navigationEvents.js.map +1 -1
  133. package/lib/module/api/linking/linking.js +11 -0
  134. package/lib/module/api/linking/linking.js.map +1 -1
  135. package/lib/module/api/screens/defineScreen.js +45 -3
  136. package/lib/module/api/screens/defineScreen.js.map +1 -1
  137. package/lib/module/api/screens/nativeScreens.js +31 -6
  138. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  139. package/lib/module/api/screens/registerScreens.js +172 -35
  140. package/lib/module/api/screens/registerScreens.js.map +1 -1
  141. package/lib/module/api/theme/theme.js +5 -0
  142. package/lib/module/api/theme/theme.js.map +1 -1
  143. package/lib/module/engine/commands.js +21 -1
  144. package/lib/module/engine/commands.js.map +1 -1
  145. package/lib/module/engine/layout.js +7 -2
  146. package/lib/module/engine/layout.js.map +1 -1
  147. package/lib/module/engine/nativeScreens.js +9 -0
  148. package/lib/module/engine/nativeScreens.js.map +1 -1
  149. package/lib/module/engine/options.js +5 -4
  150. package/lib/module/engine/options.js.map +1 -1
  151. package/lib/module/engine/optionsChange.js +102 -0
  152. package/lib/module/engine/optionsChange.js.map +1 -0
  153. package/lib/module/engine/refresh.js +85 -0
  154. package/lib/module/engine/refresh.js.map +1 -0
  155. package/lib/module/engine/rememberedOptions.js +60 -0
  156. package/lib/module/engine/rememberedOptions.js.map +1 -1
  157. package/lib/module/engine/screens.js +26 -0
  158. package/lib/module/engine/screens.js.map +1 -1
  159. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  160. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  161. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  162. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  163. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  164. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  165. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  166. package/lib/typescript/api/index.d.ts +1 -1
  167. package/lib/typescript/api/index.d.ts.map +1 -1
  168. package/lib/typescript/api/linking/linking.d.ts +8 -0
  169. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  170. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  171. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  172. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  173. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  174. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  175. package/lib/typescript/api/theme/theme.d.ts +2 -0
  176. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  177. package/lib/typescript/engine/commands.d.ts +5 -0
  178. package/lib/typescript/engine/commands.d.ts.map +1 -1
  179. package/lib/typescript/engine/layout.d.ts.map +1 -1
  180. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  181. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  182. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  183. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  184. package/lib/typescript/engine/refresh.d.ts +35 -0
  185. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  186. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  187. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  188. package/lib/typescript/engine/screens.d.ts +7 -0
  189. package/lib/typescript/engine/screens.d.ts.map +1 -1
  190. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  191. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  192. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  193. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  194. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  195. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  196. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  197. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  198. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  200. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  201. package/metro/acceptPlugin.js +57 -0
  202. package/metro/babelTransformer.js +45 -0
  203. package/metro/index.js +36 -0
  204. package/package.json +3 -1
  205. package/src/ARCHITECTURE.md +9 -3
  206. package/src/adapters/NativeANNTurboModule.ts +7 -0
  207. package/src/api/app/AppRegistry.ts +16 -2
  208. package/src/api/app/createNavigation.ts +198 -66
  209. package/src/api/events/navigationEvents.ts +9 -3
  210. package/src/api/index.ts +1 -0
  211. package/src/api/linking/linking.ts +22 -1
  212. package/src/api/screens/defineScreen.ts +49 -5
  213. package/src/api/screens/nativeScreens.ts +40 -4
  214. package/src/api/screens/registerScreens.tsx +170 -42
  215. package/src/api/theme/theme.ts +5 -0
  216. package/src/engine/commands.ts +21 -0
  217. package/src/engine/layout.ts +11 -2
  218. package/src/engine/nativeScreens.ts +10 -1
  219. package/src/engine/options.ts +6 -8
  220. package/src/engine/optionsChange.ts +107 -0
  221. package/src/engine/refresh.ts +97 -0
  222. package/src/engine/rememberedOptions.ts +70 -0
  223. package/src/engine/screens.tsx +24 -0
  224. package/src/interfaces/ComponentEvents.ts +2 -2
  225. package/src/interfaces/options/animations.ts +40 -15
  226. package/src/interfaces/options/bottomTabs.ts +2 -2
  227. package/src/interfaces/options/buttons.ts +2 -2
  228. package/src/interfaces/options/modals.ts +6 -7
  229. package/src/interfaces/options/stack.ts +8 -3
  230. package/src/interfaces/options/topBar.ts +14 -0
  231. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  232. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  233. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  234. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  235. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  236. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -0,0 +1,185 @@
1
+ #import "ANNSwipePopInteraction.h"
2
+ #import "ANNAssert.h"
3
+ #import "ANNTransitionAnimator.h"
4
+
5
+ // A flick faster than this, in points a second, ends the swipe by its direction alone.
6
+ static const CGFloat ANNSwipeFlickSpeed = 500;
7
+
8
+ // The rest of the pop plays at most this many times as fast as its own animation.
9
+ static const CGFloat ANNSwipeMostSpeedUp = 3;
10
+
11
+ @implementation ANNSwipePopInteraction {
12
+ ANNTransitionAnimator *_strongAnimator;
13
+ __weak UIPanGestureRecognizer *_recognizer;
14
+ __weak UIView *_view;
15
+ id<UIViewControllerContextTransitioning> _context;
16
+ // The interaction keeps itself while it follows the gesture: UIKit may let it go before the finger does.
17
+ ANNSwipePopInteraction *_keep;
18
+ // The gesture ended before UIKit started the transition: the end is chosen at the start.
19
+ BOOL _endedEarly;
20
+ BOOL _gaveUpEarly;
21
+ CGFloat _completionSpeed;
22
+ }
23
+
24
+ - (instancetype)initWithAnimator:(ANNTransitionAnimator *)animator
25
+ recognizer:(UIPanGestureRecognizer *)recognizer
26
+ view:(UIView *)view {
27
+ if (self = [super init]) {
28
+ _animator = animator;
29
+ _strongAnimator = animator;
30
+ _recognizer = recognizer;
31
+ _view = view;
32
+ _completionSpeed = 1;
33
+ _keep = self;
34
+ [recognizer addTarget:self action:@selector(swipe:)];
35
+ }
36
+ return self;
37
+ }
38
+
39
+ #pragma mark - UIViewControllerInteractiveTransitioning
40
+
41
+ - (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
42
+ ANNAssertMainQueue();
43
+ ANNTransitionAnimator *animator = _strongAnimator;
44
+ if (!animator || _context) {
45
+ return;
46
+ }
47
+ _context = transitionContext;
48
+ [animator animateFollowingGesture:transitionContext];
49
+ UIGestureRecognizerState state = _recognizer.state;
50
+ BOOL moving = state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged;
51
+ if (_gaveUpEarly) {
52
+ [self endFinishing:NO speed:1];
53
+ return;
54
+ }
55
+ if (_endedEarly || !moving) {
56
+ [self letGo];
57
+ return;
58
+ }
59
+ [self follow];
60
+ }
61
+
62
+ - (CGFloat)completionSpeed {
63
+ return _completionSpeed;
64
+ }
65
+
66
+ // The frames play at an even pace to their end, so UIKit's part (the bar) does too.
67
+ - (UIViewAnimationCurve)completionCurve {
68
+ return UIViewAnimationCurveLinear;
69
+ }
70
+
71
+ - (BOOL)wantsInteractiveStart {
72
+ return YES;
73
+ }
74
+
75
+ #pragma mark - The gesture
76
+
77
+ - (void)swipe:(UIPanGestureRecognizer *)recognizer {
78
+ switch (recognizer.state) {
79
+ case UIGestureRecognizerStateBegan:
80
+ case UIGestureRecognizerStateChanged:
81
+ [self follow];
82
+ break;
83
+ case UIGestureRecognizerStateEnded:
84
+ [self letGo];
85
+ break;
86
+ case UIGestureRecognizerStateCancelled:
87
+ case UIGestureRecognizerStateFailed:
88
+ [self giveUp];
89
+ break;
90
+ default:
91
+ break;
92
+ }
93
+ }
94
+
95
+ - (void)follow {
96
+ if (!_context) {
97
+ return;
98
+ }
99
+ if (_strongAnimator.isCompleted) {
100
+ [self stop];
101
+ return;
102
+ }
103
+ CGFloat progress = [self progress];
104
+ [_strongAnimator setProgress:progress];
105
+ [_context updateInteractiveTransition:progress];
106
+ }
107
+
108
+ // The finger left: a flick decides by its direction, a slow release by how far the swipe went.
109
+ - (void)letGo {
110
+ if (!_context) {
111
+ [self endedEarlyGivingUp:NO];
112
+ return;
113
+ }
114
+ CGFloat velocity = [self alongBack:[_recognizer velocityInView:_view].x];
115
+ BOOL finish = velocity > ANNSwipeFlickSpeed ||
116
+ (velocity >= -ANNSwipeFlickSpeed && [self progress] > 0.5);
117
+ // The rest takes the finger's pace when that is quicker than the animation's: the animation would cover the width
118
+ // in its length, the finger in width / velocity.
119
+ CGFloat width = MAX(_view.bounds.size.width, 1);
120
+ BOOL withFinger = finish ? velocity > 0 : velocity < 0;
121
+ CGFloat speed = withFinger ? fabs(velocity) * _strongAnimator.length / width : 1;
122
+ [self endFinishing:finish speed:MIN(MAX(speed, 1.0), ANNSwipeMostSpeedUp)];
123
+ }
124
+
125
+ // The system took the touch (a call, a second gesture): the screen goes back.
126
+ - (void)giveUp {
127
+ if (!_context) {
128
+ [self endedEarlyGivingUp:YES];
129
+ return;
130
+ }
131
+ [self endFinishing:NO speed:1];
132
+ }
133
+
134
+ // UIKit holds an interaction it starts; one it never starts goes once the gesture ended.
135
+ - (void)endedEarlyGivingUp:(BOOL)gaveUp {
136
+ _endedEarly = YES;
137
+ _gaveUpEarly = gaveUp;
138
+ [_recognizer removeTarget:self action:@selector(swipe:)];
139
+ _keep = nil;
140
+ }
141
+
142
+ - (void)endFinishing:(BOOL)finish speed:(CGFloat)speed {
143
+ id<UIViewControllerContextTransitioning> context = _context;
144
+ ANNTransitionAnimator *animator = _strongAnimator;
145
+ [self stop];
146
+ if (!context || !animator || animator.isCompleted) {
147
+ return;
148
+ }
149
+ // UIKit reads the speed as it ends its own part of the transition.
150
+ _completionSpeed = speed;
151
+ if (finish) {
152
+ [context finishInteractiveTransition];
153
+ [animator finishWithSpeed:speed];
154
+ } else {
155
+ [context cancelInteractiveTransition];
156
+ [animator rewindWithSpeed:speed];
157
+ }
158
+ }
159
+
160
+ // The part of the width the finger went in the direction of the swipe back.
161
+ - (CGFloat)progress {
162
+ UIView *view = _view;
163
+ CGFloat width = view.bounds.size.width;
164
+ if (width <= 0) {
165
+ return 0;
166
+ }
167
+ CGFloat along = [self alongBack:[_recognizer translationInView:view].x];
168
+ return MIN(MAX(along / width, 0.0), 1.0);
169
+ }
170
+
171
+ // A move across the view, positive in the direction of the swipe back: rightward, leftward in a right-to-left layout.
172
+ - (CGFloat)alongBack:(CGFloat)x {
173
+ BOOL rightToLeft =
174
+ _view.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
175
+ return rightToLeft ? -x : x;
176
+ }
177
+
178
+ - (void)stop {
179
+ [_recognizer removeTarget:self action:@selector(swipe:)];
180
+ _context = nil;
181
+ _strongAnimator = nil;
182
+ _keep = nil;
183
+ }
184
+
185
+ @end
@@ -86,10 +86,11 @@ static BOOL ANNIsOwnQuestionOfUIKit(SEL selector) {
86
86
 
87
87
  #pragma mark - UIGestureRecognizerDelegate
88
88
 
89
- // The order: `popGesture` first (with `false` there is neither a swipe nor the hold's event), then one screen or a
90
- // stack not at rest, then the hold (its event instead of the swipe), then the edge swipe under a hidden bar begins
91
- // without UIKit's delegate, and otherwise UIKit's delegate decides (its direction, a vertical scroll of the content). A
92
- // top that isn't the stack's own (a column UIKit's split view moved in) is UIKit's.
89
+ // The order: which of the stack's swipes plays first (`swipeBackTakes:`: the stack's own while the top's pop follows
90
+ // the finger, UIKit's otherwise), then `popGesture` (with `false` there is neither a swipe nor the hold's event), then
91
+ // one screen or a stack not at rest, then the hold (its event instead of the swipe), then the edge swipe under a hidden
92
+ // bar begins without UIKit's delegate, and otherwise UIKit's delegate decides (its direction, a vertical scroll of the
93
+ // content). A top that isn't the stack's own (a column UIKit's split view moved in) is UIKit's.
93
94
  //
94
95
  // Every pan in the content is offered to the content swipe, so it is first sorted: a pan that isn't a swipe back
95
96
  // (vertical, the other way) is no attempt, and neither is one on a list that scrolls sideways, which takes the pan
@@ -97,6 +98,10 @@ static BOOL ANNIsOwnQuestionOfUIKit(SEL selector) {
97
98
  // hidden bar it refuses them all, and the swipe sorts them itself, as the edge swipe begins there without it.
98
99
  - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
99
100
  ANNStackController *stack = _stack;
101
+ // One swipe plays the pop: the stack's own while the top's pop follows the finger, UIKit's otherwise.
102
+ if (stack && ![stack swipeBackTakes:gestureRecognizer]) {
103
+ return NO;
104
+ }
100
105
  if (stack && ![stack swipeBackIsUIKits:gestureRecognizer]) {
101
106
  if (!_enabled || ![stack swipeBackMayBegin]) {
102
107
  return NO;
@@ -191,6 +196,25 @@ static BOOL ANNIsOwnQuestionOfUIKit(SEL selector) {
191
196
  return YES;
192
197
  }
193
198
 
199
+ // A list under the stack's own swipe waits for it, as for UIKit's edge swipe: a swipe from the edge goes back, also on
200
+ // a list that scrolls sideways. UIKit's swipes keep UIKit's answer.
201
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
202
+ shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
203
+ ANNStackController *stack = _stack;
204
+ if (stack && [stack swipeBackIsStacks:gestureRecognizer]) {
205
+ UIView *view = otherGestureRecognizer.view;
206
+ return [view isKindOfClass:UIScrollView.class] &&
207
+ otherGestureRecognizer == ((UIScrollView *)view).panGestureRecognizer;
208
+ }
209
+ id<UIGestureRecognizerDelegate> systemDelegate = _systemDelegate;
210
+ SEL question = @selector(gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:);
211
+ if ([systemDelegate respondsToSelector:question]) {
212
+ return [systemDelegate gestureRecognizer:gestureRecognizer
213
+ shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer];
214
+ }
215
+ return NO;
216
+ }
217
+
194
218
  #pragma mark - The rest goes to UIKit's delegate
195
219
 
196
220
  - (void)setBarHidden:(BOOL)barHidden {
@@ -3,6 +3,7 @@
3
3
  #import "ANNOptions.h"
4
4
  #import "ANNSetStackRootAnimationOptions.h"
5
5
  #import "ANNStackAnimationOptions.h"
6
+ #import "ANNStackPopAnimationOptions.h"
6
7
  #import "ANNStackPushAnimationOptions.h"
7
8
 
8
9
  NS_ASSUME_NONNULL_BEGIN
@@ -11,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
11
12
  @interface ANNAnimationOptions : ANNOptions
12
13
 
13
14
  @property(nonatomic, strong) ANNStackPushAnimationOptions *push;
14
- @property(nonatomic, strong) ANNStackAnimationOptions *pop;
15
+ @property(nonatomic, strong) ANNStackPopAnimationOptions *pop;
15
16
  @property(nonatomic, strong) ANNSetStackRootAnimationOptions *setStackRoot;
16
17
  @property(nonatomic, strong) ANNEnterExitScreenAnimationOptions *setRoot;
17
18
  @property(nonatomic, strong) ANNModalAnimationOptions *showModal;
@@ -5,7 +5,7 @@
5
5
  + (NSArray<ANNOptionField *> *)optionFields {
6
6
  return @[
7
7
  [ANNOptionField groupField:@"push" class:ANNStackPushAnimationOptions.class],
8
- [ANNOptionField groupField:@"pop" class:ANNStackAnimationOptions.class],
8
+ [ANNOptionField groupField:@"pop" class:ANNStackPopAnimationOptions.class],
9
9
  [ANNOptionField groupField:@"setStackRoot" class:ANNSetStackRootAnimationOptions.class],
10
10
  [ANNOptionField groupField:@"setRoot" class:ANNEnterExitScreenAnimationOptions.class],
11
11
  [ANNOptionField groupField:@"showModal" class:ANNModalAnimationOptions.class],
@@ -58,6 +58,13 @@ void ANNCheckOptionClassesFrom(Class root);
58
58
  /// The group has any value.
59
59
  - (BOOL)hasValue;
60
60
 
61
+ /// Takes the value at `path` out, in place: the JSON keys from this group, the last one a value, an array or a whole
62
+ /// group, which gets what a freshly made group has there (unset). A key of a text's font goes through the text. A
63
+ /// module's group (an empty field table) is walked by its properties named as the keys. NO, and nothing changes, when
64
+ /// a key of the path is none of the group's or a key before the last is no group. Only on a copy of own options
65
+ /// (`changeOwnOptions:`), never on options in use.
66
+ - (BOOL)clearValueAtPath:(NSArray<NSString *> *)path;
67
+
61
68
  /// A deep copy: a new group of the same class with these merged into it. The copy shares values, never groups.
62
69
  - (instancetype)copy;
63
70
 
@@ -398,6 +398,51 @@ static NSArray *ANNCopyItems(NSArray *items) {
398
398
  return NO;
399
399
  }
400
400
 
401
+ // Generic over the whole tree, so that any option an edit takes out (`clearOptions`) goes without a list of options:
402
+ // the field table names each key's property; a module's group, which reads its JSON itself, names its properties as
403
+ // the keys.
404
+ - (BOOL)clearValueAtPath:(NSArray<NSString *> *)path {
405
+ NSString *key = path.firstObject;
406
+ if (key.length == 0) {
407
+ return NO;
408
+ }
409
+ NSArray<ANNOptionField *> *fields = ANNFieldsOf(self.class);
410
+ NSString *property = nil;
411
+ if (fields.count) {
412
+ for (ANNOptionField *field in fields) {
413
+ if ([field.key isEqualToString:key]) {
414
+ property = field.property;
415
+ break;
416
+ }
417
+ }
418
+ if (!property) {
419
+ // A group without a key (the font of a text) holds keys of its parent's object.
420
+ for (ANNOptionField *field in fields) {
421
+ if (field.kind == ANNOptionFieldKindGroup && !field.key &&
422
+ [[self valueForKey:field.property] clearValueAtPath:path]) {
423
+ return YES;
424
+ }
425
+ }
426
+ return NO;
427
+ }
428
+ } else if ([ANNValuePropertiesOf(self.class) containsObject:key]) {
429
+ property = key;
430
+ } else {
431
+ return NO;
432
+ }
433
+ if (path.count == 1) {
434
+ ANNOptions *fresh = [[self.class alloc] initWithDict:nil];
435
+ [self setValue:[fresh valueForKey:property] forKey:property];
436
+ return YES;
437
+ }
438
+ id value = [self valueForKey:property];
439
+ if (![value isKindOfClass:ANNOptions.class]) {
440
+ return NO;
441
+ }
442
+ NSArray<NSString *> *rest = [path subarrayWithRange:NSMakeRange(1, path.count - 1)];
443
+ return [(ANNOptions *)value clearValueAtPath:rest];
444
+ }
445
+
401
446
  - (id)copyWithZone:(NSZone *)zone {
402
447
  ANNOptions *copy = [[self.class alloc] initWithDict:nil];
403
448
  [copy mergeOptions:self];
@@ -52,8 +52,7 @@ static void ANNCheckSameValues(void) {
52
52
  NSDictionary *json = @{
53
53
  @"topBar" : @{
54
54
  @"title" : @{@"text" : @"Home", @"color" : @(0xFF112233)},
55
- @"rightButtonColor" :
56
- @{@"dynamic" : @{@"light" : @(0xFF000000), @"dark" : @(0xFFFFFFFF)}},
55
+ @"rightButtonColor" : @{@"light" : @(0xFF000000), @"dark" : @(0xFFFFFFFF)},
57
56
  @"leftButtonColor" : NSNull.null,
58
57
  @"rightButtons" : @[ button ],
59
58
  @"leftButtons" : @[],
@@ -75,8 +74,7 @@ static void ANNCheckSameValues(void) {
75
74
  NSDictionary<NSString *, NSArray *> *changes = @{
76
75
  @"a text" : @[ @[ @"topBar", @"title", @"text" ], @"Profile" ],
77
76
  @"a color" : @[ @[ @"topBar", @"title", @"color" ], @(0xFF112234) ],
78
- @"a side of a color pair" :
79
- @[ @[ @"topBar", @"rightButtonColor", @"dynamic", @"dark" ], @(0xFFEEEEEE) ],
77
+ @"a side of a color pair" : @[ @[ @"topBar", @"rightButtonColor", @"dark" ], @(0xFFEEEEEE) ],
80
78
  @"a number" : @[ @[ @"topBar", @"height" ], @45 ],
81
79
  @"an image" : @[ @[ @"topBar", @"rightButtons", @0, @"icon", @"system" ], @"heart" ],
82
80
  @"a boolean" : @[ @[ @"topBar", @"rightButtons", @0, @"enabled" ], @NO ],
@@ -0,0 +1,13 @@
1
+ #import "ANNStackAnimationOptions.h"
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// `animations.pop` (`StackPopAnimationOptions`).
6
+ @interface ANNStackPopAnimationOptions : ANNStackAnimationOptions
7
+
8
+ /// The swipe back plays the screen's own pop under the finger.
9
+ @property(nonatomic, strong) Bool *interactive;
10
+
11
+ @end
12
+
13
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,11 @@
1
+ #import "ANNStackPopAnimationOptions.h"
2
+
3
+ @implementation ANNStackPopAnimationOptions
4
+
5
+ + (NSArray<ANNOptionField *> *)optionFields {
6
+ return [[super optionFields] arrayByAddingObjectsFromArray:@[
7
+ [ANNOptionField boolField:@"interactive"],
8
+ ]];
9
+ }
10
+
11
+ @end
@@ -2,9 +2,10 @@
2
2
 
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
5
- /// Reads `key` of the options' JSON as a color, in the forms JS sends on iOS: an ARGB number, `{ dynamic: { light, dark
6
- /// } }` with a number or a platform color on each side, `{ semantic: [...] }`, and `'NoColor'` (or `null`) for "no
7
- /// color". A value it can't read is no value and a warning in development.
5
+ /// Reads `key` of the options' JSON as a color, in the forms JS sends on iOS: an ARGB number, `{ light, dark }` (or an
6
+ /// app's `DynamicColorIOS`, `{ dynamic: { light, dark } }`) with a number or a platform color on each side,
7
+ /// `{ semantic: [...] }`, and `'NoColor'` (or `null`) for "no color". A value it can't read is no value and a warning
8
+ /// in development.
8
9
  @interface ColorParser : NSObject
9
10
 
10
11
  + (Color *)parse:(nullable NSDictionary *)json key:(NSString *)key;
@@ -49,6 +49,24 @@ static UIColor *ANNOneColor(id value) {
49
49
  return nil;
50
50
  }
51
51
 
52
+ // A pair comes as `{ light, dark }`, either side may be left out; an app's own `DynamicColorIOS` as
53
+ // `{ dynamic: { light, dark } }`. Anything else is not a pair.
54
+ static NSDictionary *ANNColorPair(id value) {
55
+ if (![value isKindOfClass:NSDictionary.class]) {
56
+ return nil;
57
+ }
58
+ id dynamic = value[@"dynamic"];
59
+ if (dynamic) {
60
+ return [dynamic isKindOfClass:NSDictionary.class] ? dynamic : nil;
61
+ }
62
+ for (id side in value) {
63
+ if (![side isEqual:@"light"] && ![side isEqual:@"dark"]) {
64
+ return nil;
65
+ }
66
+ }
67
+ return value;
68
+ }
69
+
52
70
  @implementation ColorParser
53
71
 
54
72
  + (Color *)parse:(NSDictionary *)json key:(NSString *)key {
@@ -64,8 +82,8 @@ static UIColor *ANNOneColor(id value) {
64
82
  return [[Color alloc] initWithColor:color];
65
83
  }
66
84
  NSString *path = [ANNOptionsReader pathOfKey:key];
67
- NSDictionary *pair = [value isKindOfClass:NSDictionary.class] ? value[@"dynamic"] : nil;
68
- if ([pair isKindOfClass:NSDictionary.class]) {
85
+ NSDictionary *pair = ANNColorPair(value);
86
+ if (pair) {
69
87
  UIColor *light = [self side:pair[@"light"] name:@"light" path:path];
70
88
  UIColor *dark = [self side:pair[@"dark"] name:@"dark" path:path];
71
89
  if (light || dark) {
@@ -77,8 +95,8 @@ static UIColor *ANNOneColor(id value) {
77
95
  return [[Color alloc] initWithValue:nil];
78
96
  }
79
97
 
80
- // A side of `{ dynamic: { light, dark } }`. A side that is not there is a partial pair and is silent (the sides are
81
- // optional); a side that is there but can't be read is missing too, with a warning.
98
+ // A side of `{ light, dark }`. A side that is not there is a partial pair and is silent (the sides are optional); a
99
+ // side that is there but can't be read is missing too, with a warning.
82
100
  + (UIColor *)side:(id)value name:(NSString *)name path:(NSString *)path {
83
101
  if (!value || value == NSNull.null) {
84
102
  return nil;
@@ -8,6 +8,14 @@ NS_ASSUME_NONNULL_BEGIN
8
8
  /// every change on.
9
9
  @interface ANNSurfaceHostingView : RCTSurfaceHostingView
10
10
 
11
+ /// A size transition of the window (a rotation, a new window size) runs until its coordinator completes. Meanwhile React
12
+ /// content takes its new size without animation (`layOutContent:`). Each call ends with its own transition; main queue.
13
+ + (void)followSizeTransition:(id<UIViewControllerTransitionCoordinator>)coordinator;
14
+
15
+ /// Runs a layout that gives React content a new size: without animation while a size transition runs, as it is
16
+ /// otherwise. Main queue.
17
+ + (void)layOutContent:(void (^)(void))layout;
18
+
11
19
  /// Called at each mount of the hosted surface: after a change of the surface's size, the first one lays the surface
12
20
  /// out again, so a render that was on its way during the change doesn't keep the old size's layout.
13
21
  - (void)surfaceDidMount;
@@ -11,6 +11,9 @@ static const void *ANNSurfaceStopperKey = &ANNSurfaceStopperKey;
11
11
  // The hosting views whose surface stops at its start; each keeps its surface. Main queue.
12
12
  static NSMutableSet<ANNSurfaceHostingView *> *ANNHostingViewsStoppingAtStart;
13
13
 
14
+ // The size transitions of the window that run now. Main queue.
15
+ static NSInteger ANNSizeTransitionCount;
16
+
14
17
  // The delegate of a surface its hosting view let go at the limit: the surface keeps it, and it keeps nothing, so
15
18
  // neither holds the other alive. A surface that something else still starts (a start already on its way holds its
16
19
  // surface; React Native restarts every surface it knows after a reload, holding it) stops at once, instead of running
@@ -60,6 +63,28 @@ static NSMutableSet<ANNSurfaceHostingView *> *ANNHostingViewsStoppingAtStart;
60
63
 
61
64
  #pragma mark - The surface's size
62
65
 
66
+ // UIKit lays the screens out inside the animation of a rotation or a new window size, and React Native commits and
67
+ // mounts the new layout synchronously inside the layout that gives it the size: every frame of its views animated
68
+ // from the old size to the new one, and what a view draws itself (an SVG, a glass view, text) was drawn once at the new
69
+ // size and stretched along that animation. During such a transition the content takes its new layout at once; the
70
+ // hosting view itself still follows the animation. Other animated size changes (a sheet's detent, a split view's
71
+ // column) keep the content's frames animated, so content pinned to the moving edge rides along with it.
72
+ + (void)followSizeTransition:(id<UIViewControllerTransitionCoordinator>)coordinator {
73
+ ANNSizeTransitionCount++;
74
+ [coordinator animateAlongsideTransition:nil
75
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
76
+ ANNSizeTransitionCount--;
77
+ }];
78
+ }
79
+
80
+ + (void)layOutContent:(void (^)(void))layout {
81
+ if (ANNSizeTransitionCount > 0) {
82
+ [UIView performWithoutAnimation:layout];
83
+ } else {
84
+ layout();
85
+ }
86
+ }
87
+
63
88
  // React Native gives the surface its size here. A size change is noted for the next mount (see below). The note is
64
89
  // put aside during the call: the change's own commit mounts inside it and must not take an earlier note, or a size
65
90
  // that changes every frame (a sheet dragged between detents, a window resized) would lay out three times a frame. A
@@ -70,7 +95,9 @@ static NSMutableSet<ANNSurfaceHostingView *> *ANNHostingViewsStoppingAtStart;
70
95
  CGSize maximumSize = surface.maximumSize;
71
96
  BOOL sizeChangedBefore = _surfaceSizeChanged;
72
97
  _surfaceSizeChanged = NO;
73
- [super layoutSubviews];
98
+ [ANNSurfaceHostingView layOutContent:^{
99
+ [super layoutSubviews];
100
+ }];
74
101
  BOOL sizeChanged = surface && (!CGSizeEqualToSize(minimumSize, surface.minimumSize) ||
75
102
  !CGSizeEqualToSize(maximumSize, surface.maximumSize));
76
103
  _surfaceSizeChanged = sizeChanged || sizeChangedBefore;
@@ -3,6 +3,7 @@
3
3
  #import "ANNScreenAppearance.h"
4
4
  #import "ANNScreenReadiness.h"
5
5
  #import "ANNSlotHost.h"
6
+ #import "ANNSurfaceHostingView.h"
6
7
  #import "../../cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h"
7
8
  #import <React/RCTConstants.h>
8
9
  #import <React/RCTSurfacePresenter.h>
@@ -62,12 +63,17 @@
62
63
  return nil;
63
64
  }
64
65
 
65
- // The slot lays its content out at this size: every change goes to it (rotation, a sheet detent, the bars).
66
+ // The slot lays its content out at this size: every change goes to it (rotation, a sheet detent, the bars). Its state
67
+ // update mounts synchronously, here: during a rotation the content takes its new layout at once, as a screen's surface
68
+ // does (`+[ANNSurfaceHostingView layOutContent:]`).
66
69
  - (void)layoutSubviews {
67
70
  [super layoutSubviews];
68
71
  if (!CGSizeEqualToSize(self.bounds.size, _lastSize)) {
69
72
  _lastSize = self.bounds.size;
70
- [ANNSlotContainers containerDidChange:_componentId];
73
+ NSString *componentId = _componentId;
74
+ [ANNSurfaceHostingView layOutContent:^{
75
+ [ANNSlotContainers containerDidChange:componentId];
76
+ }];
71
77
  }
72
78
  }
73
79
 
@@ -1,6 +1,7 @@
1
1
  #import "ANNTurboModule.h"
2
2
  #import "ANNAppDirection.h"
3
3
  #import "ANNArrangementState.h"
4
+ #import "ANNArrangementViewController.h"
4
5
  #import "ANNCommandRunner.h"
5
6
  #import "ANNComponentViewController.h"
6
7
  #import "ANNDeepLinkQueue.h"
@@ -245,6 +246,51 @@ RCT_EXPORT_MODULE(ANNTurboModule)
245
246
  }];
246
247
  }
247
248
 
249
+ // `clearOptions` (development: Fast Refresh took options out of a screen that shows): the values at the paths leave
250
+ // the node's own options, so it resolves to what its parents and the defaults give there, and what shows applies the
251
+ // new resolution as after `setDefaultOptions`. The window and the node are found as for `mergeOptions`; an unknown
252
+ // id, or no path that names an option, does nothing, without a log. The user layer stays.
253
+ - (void)clearOptions:(NSString *)componentId paths:(NSArray *)paths {
254
+ NSString *what = ANNWhat(@"clear the options of the screen '%@'", componentId);
255
+ [ANNCommandRunner runVoid:@"clearOptions"
256
+ what:what
257
+ body:^{
258
+ if (!ApexNavigation.mainWindow) {
259
+ [ANNCommandRunner logFailure:@"clearOptions"
260
+ text:ANNErrorTexts.nowhereToShow];
261
+ return;
262
+ }
263
+ UIViewController *node = ANNCommandNode(componentId);
264
+ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 270100
265
+ // The options set with a pair's id are its primary's, as for `mergeOptions`.
266
+ if (@available(iOS 27.1, *)) {
267
+ if ([node isKindOfClass:ANNArrangementViewController.class]) {
268
+ node = ((ANNArrangementViewController *)node).primary;
269
+ }
270
+ }
271
+ #endif
272
+ NSMutableArray<NSArray<NSString *> *> *keyPaths = [NSMutableArray new];
273
+ for (id path in [paths isKindOfClass:NSArray.class] ? paths : @[]) {
274
+ if ([path isKindOfClass:NSString.class] && [path length] > 0) {
275
+ [keyPaths addObject:[path componentsSeparatedByString:@"."]];
276
+ }
277
+ }
278
+ if (!node || keyPaths.count == 0) {
279
+ return;
280
+ }
281
+ __block BOOL cleared = NO;
282
+ [node changeOwnOptions:^(ANNNavigationOptions *options) {
283
+ for (NSArray<NSString *> *keyPath in keyPaths) {
284
+ cleared = [options clearValueAtPath:keyPath] || cleared;
285
+ }
286
+ }];
287
+ // Paths that name no option change nothing that shows.
288
+ if (cleared) {
289
+ [ANNOptionsRepaint applyNewDefaultOptions:ANNDefaultOptions.current];
290
+ }
291
+ }];
292
+ }
293
+
248
294
  #pragma mark - Stack
249
295
 
250
296
  - (void)push:(NSString *)commandId
@@ -0,0 +1,20 @@
1
+ #include "ANNSlotState.h"
2
+
3
+ namespace facebook::react {
4
+
5
+ std::mutex &ANNSlotContentHeights::mutex() {
6
+ static std::mutex value;
7
+ return value;
8
+ }
9
+
10
+ std::unordered_map<std::string, Float> &ANNSlotContentHeights::heights() {
11
+ static std::unordered_map<std::string, Float> value;
12
+ return value;
13
+ }
14
+
15
+ std::atomic<int> &ANNSlotContentHeights::requested() {
16
+ static std::atomic<int> value{0};
17
+ return value;
18
+ }
19
+
20
+ } // namespace facebook::react
@@ -82,20 +82,11 @@ class ANNSlotContentHeights final {
82
82
  }
83
83
 
84
84
  private:
85
- static std::mutex &mutex() {
86
- static std::mutex value;
87
- return value;
88
- }
89
-
90
- static std::unordered_map<std::string, Float> &heights() {
91
- static std::unordered_map<std::string, Float> value;
92
- return value;
93
- }
94
-
95
- static std::atomic<int> &requested() {
96
- static std::atomic<int> value{0};
97
- return value;
98
- }
85
+ // One store for the whole app, in ANNSlotState.cpp: on Android the slot's layout runs in the app's library and the
86
+ // native side reads the heights from the library's own, and an inline static would give each its own copy.
87
+ static std::mutex &mutex();
88
+ static std::unordered_map<std::string, Float> &heights();
89
+ static std::atomic<int> &requested();
99
90
  };
100
91
 
101
92
  } // namespace facebook::react