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

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 (182) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/android/CLAUDE.md +14 -12
  4. package/android/CORE.md +9 -6
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
  7. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
  8. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
  9. package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
  10. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
  11. package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
  12. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
  13. package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
  14. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
  15. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
  16. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
  17. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
  18. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
  19. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
  20. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
  21. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
  22. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
  23. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +4 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
  33. package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
  34. package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
  35. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
  36. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
  37. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
  38. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
  46. package/docs-md/architecture/transitions.md +4 -2
  47. package/docs-md/containers/modals.md +2 -0
  48. package/docs-md/containers/overlays.md +1 -1
  49. package/docs-md/containers/side-menu.md +1 -1
  50. package/docs-md/containers/tabs.md +2 -0
  51. package/docs-md/flows/bottom-sheets.md +1 -1
  52. package/docs-md/fundamentals/starting-the-app.md +1 -1
  53. package/docs-md/help/glossary.md +2 -2
  54. package/docs-md/help/troubleshooting.md +21 -0
  55. package/docs-md/index.json +12 -0
  56. package/docs-md/migration/from-expo-router.md +1 -1
  57. package/docs-md/migration/from-react-native-navigation.md +1 -1
  58. package/docs-md/migration/from-react-navigation.md +2 -2
  59. package/docs-md/screen-ui/back-and-gestures.md +14 -0
  60. package/docs-md/screen-ui/bar-buttons.md +4 -4
  61. package/docs-md/screen-ui/search.md +1 -1
  62. package/docs-md/screen-ui/system-ui.md +16 -11
  63. package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
  64. package/docs-md/screen-ui/top-bar.md +8 -4
  65. package/ios/Animations/ANNFrameDriver.mm +2 -0
  66. package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
  67. package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
  68. package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
  69. package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
  70. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
  71. package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
  72. package/ios/CLAUDE.md +17 -15
  73. package/ios/CORE.md +24 -20
  74. package/ios/Contract/ANNCommandRunner.h +8 -7
  75. package/ios/Contract/ANNCommandRunner.mm +21 -27
  76. package/ios/Contract/ANNLayoutNode.h +3 -3
  77. package/ios/Contract/ANNLayoutReader.h +3 -3
  78. package/ios/Core/ANNJavaScriptCalls.h +11 -12
  79. package/ios/Core/ANNJavaScriptCalls.mm +7 -16
  80. package/ios/Core/ANNReloadCoordinator.mm +4 -8
  81. package/ios/Core/ApexNavigation+Internal.h +1 -1
  82. package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
  83. package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
  84. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
  85. package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
  86. package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
  87. package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
  88. package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
  89. package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
  90. package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
  91. package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
  92. package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
  93. package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
  94. package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
  95. package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
  96. package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
  97. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
  98. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
  99. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
  100. package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
  101. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
  102. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
  103. package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
  104. package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
  105. package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
  106. package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
  107. package/ios/Options/ANNBottomTabsOptions.h +2 -0
  108. package/ios/Options/ANNBottomTabsOptions.mm +1 -0
  109. package/ios/Options/ANNOptions.h +18 -0
  110. package/ios/Options/ANNOptions.mm +112 -1
  111. package/ios/Options/ANNOptionsReader.mm +110 -0
  112. package/ios/Options/Params/ANNParam.h +15 -0
  113. package/ios/Options/Params/ANNParam.mm +61 -0
  114. package/ios/Options/Params/Color.h +3 -0
  115. package/ios/Options/Params/Color.mm +21 -0
  116. package/ios/Options/Params/Image.h +4 -0
  117. package/ios/Options/Params/Image.mm +20 -0
  118. package/ios/React/ANNReactView.mm +6 -25
  119. package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
  120. package/ios/React/ANNSurfaceHostingView.h +6 -0
  121. package/ios/React/ANNSurfaceHostingView.mm +116 -0
  122. package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
  123. package/ios/TopBar/ANNBarAppearance.h +2 -1
  124. package/ios/TopBar/ANNBarAppearance.mm +111 -30
  125. package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
  126. package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
  127. package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
  128. package/ios/TopBar/ANNBarButtons.h +5 -3
  129. package/ios/TopBar/ANNBarButtons.mm +186 -17
  130. package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
  131. package/ios/TopBar/ANNMainList.h +3 -0
  132. package/ios/TopBar/ANNMainList.mm +4 -0
  133. package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
  134. package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
  135. package/ios/TopBar/ANNSearchPresenter.mm +2 -2
  136. package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
  137. package/ios/TopBar/ANNTopBarPresenter.h +3 -2
  138. package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
  139. package/ios/TurboModules/ANNTurboModule.mm +20 -16
  140. package/lib/module/ARCHITECTURE.md +8 -4
  141. package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
  142. package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
  143. package/lib/module/api/options/OptionsChain.js +2 -2
  144. package/lib/module/engine/commands.js +21 -1
  145. package/lib/module/engine/commands.js.map +1 -1
  146. package/lib/module/engine/screens.js +29 -4
  147. package/lib/module/engine/screens.js.map +1 -1
  148. package/lib/typescript/api/app/createNavigation.d.ts +3 -3
  149. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
  150. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
  151. package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
  152. package/lib/typescript/engine/commands.d.ts.map +1 -1
  153. package/lib/typescript/engine/screens.d.ts +20 -6
  154. package/lib/typescript/engine/screens.d.ts.map +1 -1
  155. package/lib/typescript/interfaces/options/animations.d.ts +12 -7
  156. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  157. package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
  158. package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
  159. package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
  160. package/lib/typescript/interfaces/options/system.d.ts +8 -5
  161. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  162. package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
  163. package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
  164. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  165. package/package.json +1 -1
  166. package/src/ARCHITECTURE.md +8 -4
  167. package/src/api/app/createNavigation.ts +3 -3
  168. package/src/api/hooks/useSafeAreaInsets.ts +4 -3
  169. package/src/api/options/OptionsChain.ts +2 -2
  170. package/src/engine/commands.ts +30 -6
  171. package/src/engine/screens.tsx +29 -8
  172. package/src/interfaces/options/animations.ts +12 -7
  173. package/src/interfaces/options/bottomTabs.ts +18 -5
  174. package/src/interfaces/options/buttons.ts +8 -8
  175. package/src/interfaces/options/system.ts +8 -5
  176. package/src/interfaces/options/toolbar.ts +2 -2
  177. package/src/interfaces/options/topBar.ts +32 -29
  178. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
  179. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
  180. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
  181. package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
  182. package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
@@ -8,29 +8,9 @@
8
8
  #import "ANNSurfaceHostingView.h"
9
9
  #import <React/RCTFabricSurface.h>
10
10
  #import <React/RCTSurfacePresenter.h>
11
- #import <React/RCTSurfaceStage.h>
12
11
  #import <React/RCTSurfaceView.h>
13
12
  #import <ReactCommon/RCTHost.h>
14
13
 
15
- // A surface that still starts can't be stopped yet: it is checked this often, at most this many times (5 s), and then
16
- // stopped anyway.
17
- static const NSTimeInterval ANNSurfaceStopCheckInterval = 0.05;
18
- static const NSUInteger ANNSurfaceStopCheckLimit = 100;
19
-
20
- // Stops `surface` once it runs. The block holds the surface and the view that hosts it until then: a hosting view
21
- // released earlier would stop the surface in its `dealloc`, maybe off the main queue.
22
- static void ANNStopSurfaceWhenRunning(RCTFabricSurface *surface, UIView *hostingView,
23
- NSUInteger checksLeft) {
24
- if (RCTSurfaceStageIsRunning(surface.stage) || checksLeft == 0) {
25
- [surface stop];
26
- return;
27
- }
28
- int64_t delay = (int64_t)(ANNSurfaceStopCheckInterval * NSEC_PER_SEC);
29
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
30
- ANNStopSurfaceWhenRunning(surface, hostingView, checksLeft - 1);
31
- });
32
- }
33
-
34
14
  @interface ANNReactView () <RCTSurfacePresenterObserver, ANNScreenReadinessListener>
35
15
  @end
36
16
 
@@ -359,14 +339,15 @@ static void ANNStopSurfaceWhenRunning(RCTFabricSurface *surface, UIView *hosting
359
339
  _reactViewReadyBlock = nil;
360
340
  _mountHandlers = nil;
361
341
  [_contentDidMountHandlers removeAllObjects];
362
- RCTFabricSurface *surface = _surface;
363
- if (surface) {
342
+ ANNSurfaceHostingView *hostingView = _hostingView;
343
+ if (_surface && hostingView) {
364
344
  // On the next turn of the main queue: a destroy that runs inside React Native's mounting (a wait that ends in
365
345
  // a mount, a root that replaces at once) would stop the surface there, React Native would drop its last
366
- // transaction and recycle its root view with the old content still in it, to show in a later screen.
367
- UIView *hostingView = _hostingView;
346
+ // transaction and recycle its root view with the old content still in it, to show in a later screen. The
347
+ // hosting view stops the surface then, or at its start when it doesn't run yet; the block keeps the hosting
348
+ // view, and with it the surface, until it asks.
368
349
  dispatch_async(dispatch_get_main_queue(), ^{
369
- ANNStopSurfaceWhenRunning(surface, hostingView, ANNSurfaceStopCheckLimit);
350
+ [hostingView stopSurfaceWhenRunning];
370
351
  });
371
352
  }
372
353
  }
@@ -1,11 +1,14 @@
1
1
  #import "ANNSafeAreaViewComponentView.h"
2
- #import "ANNScreenInsets.h"
2
+ #import "ANNScreenEnvironment.h"
3
3
  #import "ANNScreenReadiness.h"
4
4
  #import "../cpp/react/renderer/components/ApexNavigationSpec/ANNSafeAreaViewComponentDescriptor.h"
5
5
  #import <React/RCTConversions.h>
6
6
 
7
7
  using namespace facebook::react;
8
8
 
9
+ @interface ANNSafeAreaViewComponentView () <ANNScreenPaddingListener>
10
+ @end
11
+
9
12
  @implementation ANNSafeAreaViewComponentView {
10
13
  ANNSafeAreaViewShadowNode::ConcreteState::Shared _state;
11
14
  NSString *_screenId;
@@ -22,21 +25,13 @@ using namespace facebook::react;
22
25
  if (self = [super initWithFrame:frame]) {
23
26
  static const auto defaultProps = std::make_shared<const ANNSafeAreaViewProps>();
24
27
  _props = defaultProps;
25
- [NSNotificationCenter.defaultCenter addObserver:self
26
- selector:@selector(paddingDidChange:)
27
- name:ANNScreenSafeAreaPaddingDidChangeNotification
28
- object:nil];
29
28
  }
30
29
  return self;
31
30
  }
32
31
 
33
- - (void)dealloc {
34
- [NSNotificationCenter.defaultCenter removeObserver:self];
35
- }
36
-
37
32
  - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
38
33
  const auto &newProps = static_cast<const ANNSafeAreaViewProps &>(*props);
39
- _screenId = RCTNSStringFromStringNilIfEmpty(newProps.screenId);
34
+ [self listenToScreenId:RCTNSStringFromStringNilIfEmpty(newProps.screenId)];
40
35
  // The screen's readiness comes from this view, never from its root's children.
41
36
  if (_screenId != nil) {
42
37
  [ANNScreenReadiness screenHasSafeAreaView:_screenId];
@@ -70,18 +65,33 @@ using namespace facebook::react;
70
65
  [self applyPadding];
71
66
  }
72
67
 
73
- - (void)paddingDidChange:(NSNotification *)notification {
74
- if ([notification.object isEqual:_screenId]) {
75
- [self applyPadding];
68
+ // The view hears the padding changes of its screen only. It listens before it applies the padding it has, at each
69
+ // props, so a change between the two can't be missed.
70
+ - (void)listenToScreenId:(nullable NSString *)screenId {
71
+ if ((screenId == nil && _screenId == nil) || [screenId isEqualToString:_screenId]) {
72
+ return;
73
+ }
74
+ if (_screenId != nil) {
75
+ [ANNScreenEnvironment removePaddingListener:self forScreenId:_screenId];
76
+ }
77
+ _screenId = screenId;
78
+ if (screenId != nil) {
79
+ [ANNScreenEnvironment addPaddingListener:self forScreenId:screenId];
76
80
  }
77
81
  }
78
82
 
83
+ #pragma mark - ANNScreenPaddingListener
84
+
85
+ - (void)screenPaddingDidChange {
86
+ [self applyPadding];
87
+ }
88
+
79
89
  // The padding of the screen goes to the state, which lays the view out with it.
80
90
  - (void)applyPadding {
81
91
  if (_state == nullptr || _screenId == nil) {
82
92
  return;
83
93
  }
84
- UIEdgeInsets padding = [ANNScreenInsets paddingForScreenId:_screenId];
94
+ UIEdgeInsets padding = [ANNScreenEnvironment paddingForScreenId:_screenId];
85
95
  EdgeInsets insets{.left = (Float)padding.left,
86
96
  .top = (Float)padding.top,
87
97
  .right = (Float)padding.right,
@@ -144,7 +154,8 @@ using namespace facebook::react;
144
154
  - (void)prepareForRecycle {
145
155
  [super prepareForRecycle];
146
156
  _state.reset();
147
- _screenId = nil;
157
+ // A recycled view gets its next screen's id with its next props.
158
+ [self listenToScreenId:nil];
148
159
  _readySent = NO;
149
160
  _contentRenderedSent = NO;
150
161
  _contentRendered = NO;
@@ -12,6 +12,12 @@ NS_ASSUME_NONNULL_BEGIN
12
12
  /// out again, so a render that was on its way during the change doesn't keep the old size's layout.
13
13
  - (void)surfaceDidMount;
14
14
 
15
+ /// The screen went: the hosted surface stops as soon as it runs, on the main queue. A surface that runs already stops
16
+ /// at once; one that doesn't run yet stops at its change to running (`surface:didChangeStage:`). Until then this view
17
+ /// and its surface are kept, so the core gives them up on the main queue. A surface that hasn't started after 5 s is
18
+ /// let go, and stops if it ever starts later. Once; main queue.
19
+ - (void)stopSurfaceWhenRunning;
20
+
15
21
  @end
16
22
 
17
23
  NS_ASSUME_NONNULL_END
@@ -1,9 +1,49 @@
1
1
  #import "ANNSurfaceHostingView.h"
2
+ #import "ANNAssert.h"
2
3
  #import <React/RCTFabricSurface.h>
4
+ #import <objc/runtime.h>
5
+
6
+ // How long a gone screen's surface that doesn't run yet is kept for its stop. The host starts a surface through a weak
7
+ // reference once its JavaScript ran, so a surface let go before that, which nothing else holds, goes away unstarted.
8
+ static const NSTimeInterval ANNSurfaceStartWaitLimit = 5.0;
9
+ static const void *ANNSurfaceStopperKey = &ANNSurfaceStopperKey;
10
+
11
+ // The hosting views whose surface stops at its start; each keeps its surface. Main queue.
12
+ static NSMutableSet<ANNSurfaceHostingView *> *ANNHostingViewsStoppingAtStart;
13
+
14
+ // The delegate of a surface its hosting view let go at the limit: the surface keeps it, and it keeps nothing, so
15
+ // neither holds the other alive. A surface that something else still starts (a start already on its way holds its
16
+ // surface; React Native restarts every surface it knows after a reload, holding it) stops at once, instead of running
17
+ // with no screen and going away running, which leaves its React tree registered.
18
+ @interface ANNSurfaceStopper : NSObject <RCTSurfaceDelegate>
19
+ @end
20
+
21
+ @implementation ANNSurfaceStopper
22
+
23
+ // React Native's delegate method names the deprecated `RCTSurface`; the surface is an `RCTFabricSurface`.
24
+ #pragma clang diagnostic push
25
+ #pragma clang diagnostic ignored "-Wdeprecated"
26
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
27
+ - (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage {
28
+ if (!RCTSurfaceStageIsRunning(stage)) {
29
+ return;
30
+ }
31
+ // The start tells this off the main queue, before it ends: the stop goes on the main queue, the block keeping the
32
+ // surface until then.
33
+ id<RCTSurfaceProtocol> startedSurface = (id<RCTSurfaceProtocol>)surface;
34
+ dispatch_async(dispatch_get_main_queue(), ^{
35
+ [startedSurface stop];
36
+ });
37
+ }
38
+ #pragma clang diagnostic pop
39
+
40
+ @end
3
41
 
4
42
  @implementation ANNSurfaceHostingView {
5
43
  // The surface was given a new size since its last mount.
6
44
  BOOL _surfaceSizeChanged;
45
+ // The screen went: the surface stops each time it runs. Main queue.
46
+ BOOL _stopsWhenRunning;
7
47
  }
8
48
 
9
49
  // React Native 0.87 posts the hosting view's traits as the app's scheme from here (`RCTSurfaceHostingView.mm`). On
@@ -60,6 +100,82 @@
60
100
  [surface setMinimumSize:minimumSize maximumSize:maximumSize];
61
101
  }
62
102
 
103
+ #pragma mark - The stop
104
+
105
+ // `RCTFabricSurface stop` does nothing while the surface doesn't run, and the host starts a surface later, off the main
106
+ // queue: a screen can go before that. Its surface then stops at its change to running. This view is kept until then,
107
+ // with its surface: released earlier, it would stop the surface in its `dealloc`, maybe off the main queue.
108
+ - (void)stopSurfaceWhenRunning {
109
+ ANNAssertMainQueue();
110
+ if (_stopsWhenRunning) {
111
+ return;
112
+ }
113
+ _stopsWhenRunning = YES;
114
+ RCTFabricSurface *surface = [self fabricSurface];
115
+ if (!surface) {
116
+ return;
117
+ }
118
+ if (RCTSurfaceStageIsRunning(surface.stage)) {
119
+ [surface stop];
120
+ return;
121
+ }
122
+ if (!ANNHostingViewsStoppingAtStart) {
123
+ ANNHostingViewsStoppingAtStart = [NSMutableSet new];
124
+ }
125
+ [ANNHostingViewsStoppingAtStart addObject:self];
126
+ __weak ANNSurfaceHostingView *weakSelf = self;
127
+ int64_t delay = (int64_t)(ANNSurfaceStartWaitLimit * NSEC_PER_SEC);
128
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
129
+ [weakSelf letSurfaceGo];
130
+ });
131
+ }
132
+
133
+ // The surface tells its stage off the main queue, from its start and its stop. React Native's hosting view follows it
134
+ // as before; a gone screen's surface that starts stops on the next turn of the main queue, once its start ended. The
135
+ // block keeps this view, and with it the surface, until then, so their last release is on the main queue.
136
+ #pragma clang diagnostic push
137
+ #pragma clang diagnostic ignored "-Wdeprecated"
138
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
139
+ - (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage {
140
+ [super surface:surface didChangeStage:stage];
141
+ if (!RCTSurfaceStageIsRunning(stage)) {
142
+ return;
143
+ }
144
+ dispatch_async(dispatch_get_main_queue(), ^{
145
+ [self stopSurfaceIfAsked];
146
+ });
147
+ }
148
+ #pragma clang diagnostic pop
149
+
150
+ - (void)stopSurfaceIfAsked {
151
+ if (!_stopsWhenRunning) {
152
+ return;
153
+ }
154
+ RCTFabricSurface *surface = [self fabricSurface];
155
+ if (surface && RCTSurfaceStageIsRunning(surface.stage)) {
156
+ [surface stop];
157
+ }
158
+ [ANNHostingViewsStoppingAtStart removeObject:self];
159
+ }
160
+
161
+ // The limit: the surface hasn't started. It gets a delegate of its own that stops it at any later start, and this
162
+ // view lets it go. The delegate changes before the check: a start told before the change reaches this view, which
163
+ // stops the surface (its block keeps the view); one told after it reaches the new delegate.
164
+ - (void)letSurfaceGo {
165
+ if (![ANNHostingViewsStoppingAtStart containsObject:self]) {
166
+ return;
167
+ }
168
+ RCTFabricSurface *surface = [self fabricSurface];
169
+ ANNSurfaceStopper *stopper = [ANNSurfaceStopper new];
170
+ objc_setAssociatedObject(surface, ANNSurfaceStopperKey, stopper,
171
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
172
+ surface.delegate = stopper;
173
+ if (RCTSurfaceStageIsRunning(surface.stage)) {
174
+ [surface stop];
175
+ }
176
+ [ANNHostingViewsStoppingAtStart removeObject:self];
177
+ }
178
+
63
179
  - (nullable RCTFabricSurface *)fabricSurface {
64
180
  id surface = self.surface;
65
181
  return [surface isKindOfClass:RCTFabricSurface.class] ? surface : nil;
@@ -2,6 +2,7 @@
2
2
  #import "ANNExternalViewController.h"
3
3
  #import "ANNIconButtonImage.h"
4
4
  #import "ANNStackController.h"
5
+ #import "ANNTabsDesign.h"
5
6
  #import <objc/runtime.h>
6
7
 
7
8
  // The item below that the core configured: what it set may go again.
@@ -141,10 +142,8 @@ static const void *ANNBackButtonConfiguredKey = &ANNBackButtonConfiguredKey;
141
142
  ANNBackBarButtonItem *item = [ANNBackBarButtonItem new];
142
143
  item.allowsMenu = [backButton.enableMenu withDefault:YES];
143
144
  item.style = UIBarButtonItemStylePlain;
144
- BOOL before26 = YES;
145
- if (@available(iOS 26.0, *)) {
146
- before26 = NO;
147
- }
145
+ // The compatibility mode of iOS 26 shows the title below too, as the design before it.
146
+ BOOL before26 = !ANNTabsDesign.isGlass;
148
147
  item.title = title ?: (before26 ? belowTitle : nil);
149
148
  UIColor *background = [self iconBackgroundColorOf:backButton];
150
149
  if (background) {
@@ -165,7 +164,9 @@ static const void *ANNBackButtonConfiguredKey = &ANNBackButtonConfiguredKey;
165
164
  [iconBackground.height withDefault:0])
166
165
  insets:UIEdgeInsetsZero];
167
166
  if (@available(iOS 26.0, *)) {
168
- item.hidesSharedBackground = [backButton.hideSharedBackground withDefault:YES];
167
+ if (ANNTabsDesign.isGlass) {
168
+ item.hidesSharedBackground = [backButton.hideSharedBackground withDefault:YES];
169
+ }
169
170
  }
170
171
  }
171
172
  return item;
@@ -45,7 +45,8 @@ typedef struct {
45
45
  /// (a clear color is a clear bar). The one rule for the bar, its color in the list and the top edge effect.
46
46
  + (nullable UIColor *)solidColorOf:(nullable Color *)color traits:(UITraitCollection *)traits;
47
47
 
48
- /// A 1×1 image of `color` resolved for `traits`.
48
+ /// A 1×1 image of `color` resolved for `traits`, with a variant for each style: the same image for the same resolved
49
+ /// colors, so a look filled again equals the one before (as the back button's tinted arrow).
49
50
  + (UIImage *)imageOfColor:(UIColor *)color traits:(UITraitCollection *)traits;
50
51
 
51
52
  @end
@@ -1,5 +1,12 @@
1
1
  #import "ANNBarAppearance.h"
2
2
  #import "ANNBackButtonPresenter.h"
3
+ #import "ANNTabsDesign.h"
4
+ #import <objc/runtime.h>
5
+
6
+ // The icon an indicator was tinted from: an entry found by the icon's address counts only for that icon. An indicator
7
+ // that is the icon itself (already an original image) carries nothing: the icon must not hold itself, and it may be
8
+ // the image the options' cache keeps.
9
+ static const void *ANNIndicatorIconKey = &ANNIndicatorIconKey;
3
10
 
4
11
  // UIKit's fonts of the places the options build over: the title of the row, the large title, the text of the back
5
12
  // button.
@@ -28,6 +35,27 @@ static BOOL ANNIsClear(UIColor *color, UITraitCollection *traits) {
28
35
  return CGColorGetAlpha([color resolvedColorWithTraitCollection:traits].CGColor) == 0;
29
36
  }
30
37
 
38
+ // The images the looks are made of, by what they are drawn from, so a look filled again from the same options is
39
+ // equal to the one before (UIKit compares appearances by value, images by identity) and nothing is set again. Main
40
+ // queue; a memory warning empties it.
41
+ static NSCache<id, UIImage *> *ANNLookImages(void) {
42
+ static NSCache<id, UIImage *> *images;
43
+ static dispatch_once_t once;
44
+ dispatch_once(&once, ^{
45
+ images = [NSCache new];
46
+ images.countLimit = 64;
47
+ });
48
+ return images;
49
+ }
50
+
51
+ // A color of the options as a key: a pair by its sides (its dynamic color is made anew), else its value.
52
+ static NSArray *ANNColorKeyOf(Color *color) {
53
+ if (color.light || color.dark) {
54
+ return @[ color.light ?: NSNull.null, color.dark ?: NSNull.null ];
55
+ }
56
+ return @[ color.value ?: NSNull.null ];
57
+ }
58
+
31
59
  static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions *font,
32
60
  Color *color,
33
61
  UIFont *placeFont) {
@@ -104,16 +132,14 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
104
132
  return resolved && !ANNIsClear(resolved, traits) ? resolved : nil;
105
133
  }
106
134
 
135
+ // The glass bar is translucent by default; the design before iOS 26 (and its compatibility mode) opaque.
107
136
  + (BOOL)translucentByDefault {
108
- if (@available(iOS 26.0, *)) {
109
- return YES;
110
- }
111
- return NO;
137
+ return ANNTabsDesign.isGlass;
112
138
  }
113
139
 
114
- // `color` beats `blur` and `translucent`; a clear color is a clear bar. On iOS 26 a color is a solid bar: a clear base
115
- // with the color and a 1×1 image of it, without the system effect. `blur` is the system's chrome material on a clear
116
- // base, with no view of its own in the bar.
140
+ // `color` beats `blur` and `translucent`; a clear color is a clear bar. On the glass of iOS 26 a color is a solid
141
+ // bar: a clear base with the color and a 1×1 image of it, without the system effect. `blur` is the system's chrome
142
+ // material on a clear base, with no view of its own in the bar.
117
143
  + (void)configure:(UINavigationBarAppearance *)appearance
118
144
  color:(UIColor *)color
119
145
  blur:(BOOL)blur
@@ -124,7 +150,7 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
124
150
  [appearance configureWithTransparentBackground];
125
151
  return;
126
152
  }
127
- if (@available(iOS 26.0, *)) {
153
+ if (ANNTabsDesign.isGlass) {
128
154
  [appearance configureWithTransparentBackground];
129
155
  appearance.backgroundEffect = nil;
130
156
  appearance.backgroundColor = color;
@@ -219,23 +245,51 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
219
245
  if (!color) {
220
246
  return nil;
221
247
  }
222
- icon = [UIImage systemImageNamed:@"chevron.backward"];
248
+ symbol = @"chevron.backward";
249
+ icon = [UIImage systemImageNamed:symbol];
223
250
  isSymbol = YES;
224
251
  }
252
+ // A symbol by its name; an image by its object, which the entry keeps, so its address is never another's.
253
+ id iconKey = isSymbol ? symbol : [NSValue valueWithNonretainedObject:icon];
254
+ NSArray *key = @[
255
+ @"back", iconKey, color ? ANNColorKeyOf(backButton.color) : NSNull.null
256
+ ];
257
+ NSCache<id, UIImage *> *images = ANNLookImages();
258
+ UIImage *cached = [images objectForKey:key];
259
+ if (cached && (isSymbol || cached == icon ||
260
+ objc_getAssociatedObject(cached, ANNIndicatorIconKey) == icon)) {
261
+ return cached;
262
+ }
263
+ UIImage *indicator = nil;
225
264
  if (color) {
226
- return [icon imageWithTintColor:color renderingMode:UIImageRenderingModeAlwaysOriginal];
265
+ indicator = [icon imageWithTintColor:color
266
+ renderingMode:UIImageRenderingModeAlwaysOriginal];
267
+ } else {
268
+ indicator =
269
+ isSymbol ? icon : [icon imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
270
+ }
271
+ if (!isSymbol && indicator != icon) {
272
+ objc_setAssociatedObject(indicator, ANNIndicatorIconKey, icon,
273
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
227
274
  }
228
- return isSymbol ? icon : [icon imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
275
+ [images setObject:indicator forKey:key];
276
+ return indicator;
229
277
  }
230
278
 
279
+ // One for the app: every look that hides the arrow holds the same image.
231
280
  + (UIImage *)emptyIndicator {
232
- UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
233
- format.opaque = NO;
234
- UIGraphicsImageRenderer *renderer =
235
- [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1) format:format];
236
- UIImage *empty = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context){
237
- }];
238
- return [empty imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
281
+ static UIImage *empty;
282
+ static dispatch_once_t once;
283
+ dispatch_once(&once, ^{
284
+ UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
285
+ format.opaque = NO;
286
+ UIGraphicsImageRenderer *renderer =
287
+ [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1) format:format];
288
+ UIImage *drawn = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context){
289
+ }];
290
+ empty = [drawn imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
291
+ });
292
+ return empty;
239
293
  }
240
294
 
241
295
  + (UIColor *)pinnedTitleColor:(Color *)color traits:(UITraitCollection *)traits {
@@ -244,8 +298,44 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
244
298
  }
245
299
 
246
300
  // The 1×1 picture of the color in both styles: the bar's image view picks the variant of its own style, so a `{ light,
247
- // dark }` color follows a switch of the style without a new appearance.
301
+ // dark }` color follows a switch of the style without a new appearance. Made once for the two resolved colors and the
302
+ // style picked.
248
303
  + (UIImage *)imageOfColor:(UIColor *)color traits:(UITraitCollection *)traits {
304
+ UITraitCollection *light =
305
+ [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
306
+ UITraitCollection *dark =
307
+ [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
308
+ // Each variant is drawn with the window's other traits (contrast, elevation) and registered by its style only.
309
+ NSArray<UITraitCollection *> *base = traits ? @[ traits ] : @[];
310
+ UITraitCollection *lightTraits = [UITraitCollection
311
+ traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:light]];
312
+ UITraitCollection *darkTraits = [UITraitCollection
313
+ traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:dark]];
314
+ UIColor *lightColor = [color resolvedColorWithTraitCollection:lightTraits];
315
+ UIColor *darkColor = [color resolvedColorWithTraitCollection:darkTraits];
316
+ BOOL isDark = traits.userInterfaceStyle == UIUserInterfaceStyleDark;
317
+ NSArray *key = @[ @"color", lightColor, darkColor, @(isDark) ];
318
+ NSCache<id, UIImage *> *images = ANNLookImages();
319
+ UIImage *cached = [images objectForKey:key];
320
+ if (cached) {
321
+ return cached;
322
+ }
323
+ UIImage *image = [self drawImageOf:color
324
+ light:light
325
+ dark:dark
326
+ lightTraits:lightTraits
327
+ darkTraits:darkTraits
328
+ isDark:isDark];
329
+ [images setObject:image forKey:key];
330
+ return image;
331
+ }
332
+
333
+ + (UIImage *)drawImageOf:(UIColor *)color
334
+ light:(UITraitCollection *)light
335
+ dark:(UITraitCollection *)dark
336
+ lightTraits:(UITraitCollection *)lightTraits
337
+ darkTraits:(UITraitCollection *)darkTraits
338
+ isDark:(BOOL)isDark {
249
339
  UIImage * (^draw)(UITraitCollection *) = ^UIImage *(UITraitCollection *variant) {
250
340
  UIColor *resolved = [color resolvedColorWithTraitCollection:variant];
251
341
  UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
@@ -257,20 +347,11 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
257
347
  [context fillRect:CGRectMake(0, 0, 1, 1)];
258
348
  }];
259
349
  };
260
- UITraitCollection *light =
261
- [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
262
- UITraitCollection *dark =
263
- [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
264
- // Each variant is drawn with the window's other traits (contrast, elevation) and registered by its style only.
265
- NSArray<UITraitCollection *> *base = traits ? @[ traits ] : @[];
266
- UIImage *lightImage =
267
- draw([UITraitCollection traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:light]]);
268
- UIImage *darkImage =
269
- draw([UITraitCollection traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:dark]]);
350
+ UIImage *lightImage = draw(lightTraits);
351
+ UIImage *darkImage = draw(darkTraits);
270
352
  UIImageAsset *asset = [UIImageAsset new];
271
353
  [asset registerImage:lightImage withTraitCollection:light];
272
354
  [asset registerImage:darkImage withTraitCollection:dark];
273
- BOOL isDark = traits.userInterfaceStyle == UIUserInterfaceStyleDark;
274
355
  UIImage *picked = [asset imageWithTraitCollection:isDark ? dark : light];
275
356
  return picked ?: (isDark ? darkImage : lightImage);
276
357
  }
@@ -1,4 +1,5 @@
1
1
  #import "ANNBarBackgroundComponent.h"
2
+ #import <React/RCTLog.h>
2
3
  #import <objc/runtime.h>
3
4
 
4
5
  // The background a bar shows now.
@@ -13,6 +14,21 @@ static UIView *ANNBackgroundHostOf(UINavigationBar *bar) {
13
14
  return subview;
14
15
  }
15
16
  }
17
+ #if DEBUG
18
+ // The name is UIKit's private one: a system that renames the view puts the components on the bar itself. Told
19
+ // once, in development, and only for a bar that has its views already (one not laid out yet has none).
20
+ static BOOL told = NO;
21
+ if (!told && bar.subviews.count > 0) {
22
+ told = YES;
23
+ NSMutableArray<NSString *> *names = [NSMutableArray new];
24
+ for (UIView *subview in bar.subviews) {
25
+ [names addObject:NSStringFromClass(subview.class)];
26
+ }
27
+ RCTLogWarn(@"[ApexNavigation] the bar has no view named …BarBackground, so a background "
28
+ @"component goes on the bar itself at index 1; its views: %@",
29
+ [names componentsJoinedByString:@", "]);
30
+ }
31
+ #endif
16
32
  return bar;
17
33
  }
18
34
 
@@ -4,10 +4,10 @@
4
4
  NS_ASSUME_NONNULL_BEGIN
5
5
 
6
6
  /// The slot of a React button (`topBar.leftButtons[].component`) as the custom view of its bar item. It takes a content
7
- /// view (`UIView<ANNComponentProtocol>`), never a class of the roots. On iOS 26 without `UIDesignRequiresCompatibility`
8
- /// in the app's Info.plist the slot is 44 pt high and as wide as the content, at least 44 pt; the width follows the
9
- /// content both ways (measured after every mount; a change over 0.5 pt only), and the slot's constraints live with the
10
- /// view, so a new item built around it reuses them. With that key and before iOS 26: the content's size, no
7
+ /// view (`UIView<ANNComponentProtocol>`), never a class of the roots. In the glass design of iOS 26 (`ANNTabsDesign`)
8
+ /// the slot is 44 pt high and as wide as the content, at least 44 pt; the width follows the content both ways
9
+ /// (measured after every mount; a change over 0.5 pt only), and the slot's constraints live with the view, so a new
10
+ /// item built around it reuses them. In the compatibility mode and before iOS 26: the content's size, no
11
11
  /// constraints. A button rendered before the screen shows enters at its width. The core measures only the surface and
12
12
  /// never moves the views inside it: the JS wrapper centres them.
13
13
  @interface ANNBarButtonComponentView : UIView
@@ -7,7 +7,7 @@ static const CGFloat ANNBarButtonSlotHeight = 44;
7
7
  static const CGFloat ANNBarButtonSlotMinimumWidth = 44;
8
8
  static const CGFloat ANNBarButtonWidthTolerance = 0.5;
9
9
 
10
- // The slot of iOS 26's bar: unless the app keeps the earlier design (`UIDesignRequiresCompatibility`).
10
+ // The slot of iOS 26's bar: unless UIKit keeps the earlier design for the app (`ANNTabsDesign`).
11
11
  static BOOL ANNUsesSlotConstraints(void) {
12
12
  return ANNTabsDesign.isGlass;
13
13
  }
@@ -14,8 +14,9 @@ typedef NS_ENUM(NSInteger, ANNBarButtonsSide) {
14
14
 
15
15
  /// The buttons of one screen's bar and toolbar (`topBar.leftButtons[].*`, the side options, `overflowMenu`,
16
16
  /// `toolbar.buttons`): built from the resolved options, whole, at the screen's creation, at each appearance and at a
17
- /// merge of a side. Each button is an `ANNUIBarButtonItem` of the first kind its fields give (React view → icon and
18
- /// text → image button → SF Symbol → image → text → system item); a button without `id` is not placed. Side colors go
17
+ /// merge of a side, when anything the side's build reads changed or the item no longer holds what it put there.
18
+ /// Each button is an `ANNUIBarButtonItem` of the first kind its fields give (React view → icon and text → image
19
+ /// button → SF Symbol → image → text → system item); a button without `id` is not placed. Side colors go
19
20
  /// onto what is built, never into the options; what the user chose (a toggle, a menu's check) goes into the screen's
20
21
  /// user layer. The React buttons' slots are kept by component id, so a button built again reuses its slot. Main queue.
21
22
  @interface ANNBarButtons : NSObject
@@ -27,7 +28,8 @@ typedef NS_ENUM(NSInteger, ANNBarButtonsSide) {
27
28
  + (instancetype)new NS_UNAVAILABLE;
28
29
 
29
30
  /// Sets `side` of the screen's item (left, right, center) from `options`; `animated` fades new buttons in (a side set
30
- /// as groups changes at once).
31
+ /// as groups changes at once). Nothing when the side's buttons, colors, loaded images, React roots and traits are the
32
+ /// same as at its last build and the item still holds what that build set: iOS 26 animates even equal new items.
31
33
  - (void)applySide:(ANNBarButtonsSide)side
32
34
  options:(ANNNavigationOptions *)options
33
35
  animated:(BOOL)animated;