@apexnative/navigation 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (226) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/android/CLAUDE.md +38 -9
  3. package/android/CORE.md +11 -4
  4. package/android/RECIPES.md +9 -3
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
  6. package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
  7. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
  8. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
  9. package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
  10. package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
  11. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
  12. package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
  14. package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
  15. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
  16. package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
  19. package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
  23. package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
  24. package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
  25. package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
  26. package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
  27. package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
  28. package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +92 -19
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +79 -13
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +45 -6
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +35 -9
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +10 -8
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -1
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +72 -16
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +164 -13
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +26 -1
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +136 -40
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +107 -4
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +131 -9
  63. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +75 -21
  64. package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
  65. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
  66. package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
  67. package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
  68. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
  69. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
  70. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
  71. package/cli/doctor/checks/app.js +61 -13
  72. package/cli/migrate/native.js +18 -0
  73. package/cli/setup/source.js +2 -1
  74. package/cli/setup/steps/mainApplication.js +134 -19
  75. package/docs-md/architecture/transitions.md +12 -7
  76. package/docs-md/containers/modals.md +4 -2
  77. package/docs-md/containers/overlays.md +3 -1
  78. package/docs-md/containers/side-menu.md +4 -2
  79. package/docs-md/containers/tabs.md +2 -0
  80. package/docs-md/flows/forms-in-a-modal.md +1 -1
  81. package/docs-md/help/glossary.md +2 -2
  82. package/docs-md/help/troubleshooting.md +20 -3
  83. package/docs-md/index.json +12 -0
  84. package/docs-md/migration/from-react-native-navigation.md +8 -2
  85. package/docs-md/screen-ui/back-and-gestures.md +8 -2
  86. package/docs-md/screen-ui/system-ui.md +1 -1
  87. package/docs-md/start/installation.md +3 -1
  88. package/ios/CLAUDE.md +17 -5
  89. package/ios/CORE.md +13 -8
  90. package/ios/Contract/ANNCommandAnswer.mm +5 -20
  91. package/ios/Contract/ANNCommandContract.h +15 -0
  92. package/ios/Contract/ANNCommandContract.mm +78 -0
  93. package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
  94. package/ios/Core/ANNErrorHandler.h +1 -1
  95. package/ios/Core/ANNErrorHandler.mm +3 -1
  96. package/ios/Core/ANNEventEmitter.h +1 -1
  97. package/ios/Core/ANNEventEmitter.mm +2 -2
  98. package/ios/Core/ANNJavaScriptCalls.h +34 -0
  99. package/ios/Core/ANNJavaScriptCalls.mm +61 -0
  100. package/ios/Core/ANNNavigationState.h +7 -3
  101. package/ios/Core/ANNNavigationState.mm +10 -5
  102. package/ios/Core/ANNNodeHelper.h +32 -12
  103. package/ios/Core/ANNNodeHelper.mm +100 -3
  104. package/ios/Core/ANNRenderWait.h +15 -1
  105. package/ios/Core/ANNRenderWait.mm +119 -17
  106. package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
  107. package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
  108. package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
  109. package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
  110. package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
  111. package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
  112. package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
  113. package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
  114. package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
  115. package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
  116. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
  117. package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
  118. package/ios/Layouts/Stack/ANNStackController.mm +26 -3
  119. package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
  120. package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
  121. package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
  122. package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
  123. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
  124. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
  125. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
  126. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
  127. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
  128. package/ios/Options/ANNOptionsCache.h +7 -6
  129. package/ios/Options/ANNSearchBarOptions.h +4 -3
  130. package/ios/Options/ANNUserChoice.h +57 -0
  131. package/ios/Options/ANNUserChoice.mm +44 -0
  132. package/ios/Options/Params/ANNImageSource.h +61 -0
  133. package/ios/Options/Params/ANNImageSource.mm +101 -0
  134. package/ios/Options/Params/ANNOptionImages.h +37 -0
  135. package/ios/Options/Params/ANNOptionImages.mm +284 -0
  136. package/ios/Options/Params/Image.h +11 -2
  137. package/ios/Options/Params/Image.mm +15 -2
  138. package/ios/Options/Params/ImageParser.h +5 -5
  139. package/ios/Options/Params/ImageParser.mm +28 -46
  140. package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
  141. package/ios/RECIPES.md +11 -5
  142. package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
  143. package/ios/React/Slots/ANNSlotContainers.h +14 -6
  144. package/ios/React/Slots/ANNSlotContainers.mm +75 -2
  145. package/ios/TopBar/ANNBarButtons.h +9 -5
  146. package/ios/TopBar/ANNBarButtons.mm +211 -63
  147. package/ios/TopBar/ANNIconButtonImage.h +1 -0
  148. package/ios/TopBar/ANNIconButtonImage.mm +34 -1
  149. package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
  150. package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
  151. package/ios/TopBar/ANNSearchPresenter.mm +170 -19
  152. package/ios/TopBar/ANNTopBarPresenter.h +5 -0
  153. package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
  154. package/ios/TurboModules/ANNTurboModule.mm +39 -21
  155. package/lib/module/ARCHITECTURE.md +18 -18
  156. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  157. package/lib/module/api/hooks/useNavigation.js +8 -3
  158. package/lib/module/api/hooks/useNavigation.js.map +1 -1
  159. package/lib/module/api/navigation.js.map +1 -1
  160. package/lib/module/engine/commands.js +9 -7
  161. package/lib/module/engine/commands.js.map +1 -1
  162. package/lib/module/engine/layout.js +13 -9
  163. package/lib/module/engine/layout.js.map +1 -1
  164. package/lib/module/engine/options.js +44 -11
  165. package/lib/module/engine/options.js.map +1 -1
  166. package/lib/module/engine/places.js +44 -7
  167. package/lib/module/engine/places.js.map +1 -1
  168. package/lib/module/engine/props.js +30 -1
  169. package/lib/module/engine/props.js.map +1 -1
  170. package/lib/module/engine/rememberedOptions.js +1 -1
  171. package/lib/module/engine/rememberedOptions.js.map +1 -1
  172. package/lib/module/engine/screens.js +22 -12
  173. package/lib/module/engine/screens.js.map +1 -1
  174. package/lib/module/interfaces/contract/commands.json +101 -0
  175. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
  176. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  177. package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
  178. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  179. package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
  180. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  181. package/lib/typescript/api/navigation.d.ts +4 -1
  182. package/lib/typescript/api/navigation.d.ts.map +1 -1
  183. package/lib/typescript/engine/commands.d.ts.map +1 -1
  184. package/lib/typescript/engine/options.d.ts +5 -2
  185. package/lib/typescript/engine/options.d.ts.map +1 -1
  186. package/lib/typescript/engine/places.d.ts +5 -2
  187. package/lib/typescript/engine/places.d.ts.map +1 -1
  188. package/lib/typescript/engine/props.d.ts +8 -1
  189. package/lib/typescript/engine/props.d.ts.map +1 -1
  190. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  191. package/lib/typescript/engine/screens.d.ts.map +1 -1
  192. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  193. package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
  194. package/lib/typescript/interfaces/Events.d.ts +19 -10
  195. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  196. package/lib/typescript/interfaces/options/animations.d.ts +15 -7
  197. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  198. package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
  199. package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
  200. package/lib/typescript/interfaces/options/modals.d.ts +18 -13
  201. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  202. package/lib/typescript/interfaces/options/system.d.ts +6 -3
  203. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  204. package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
  205. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  206. package/package.json +1 -1
  207. package/src/ARCHITECTURE.md +18 -18
  208. package/src/adapters/NativeANNTurboModule.ts +5 -2
  209. package/src/api/events/navigationEvents.ts +1 -1
  210. package/src/api/hooks/useNavigation.ts +9 -4
  211. package/src/api/navigation.ts +4 -1
  212. package/src/engine/commands.ts +19 -7
  213. package/src/engine/layout.ts +16 -9
  214. package/src/engine/options.ts +39 -11
  215. package/src/engine/places.ts +56 -7
  216. package/src/engine/props.ts +30 -1
  217. package/src/engine/rememberedOptions.ts +4 -1
  218. package/src/engine/screens.tsx +23 -13
  219. package/src/interfaces/ComponentEvents.ts +2 -2
  220. package/src/interfaces/Events.ts +19 -10
  221. package/src/interfaces/contract/commands.json +101 -0
  222. package/src/interfaces/options/animations.ts +15 -7
  223. package/src/interfaces/options/buttons.ts +13 -5
  224. package/src/interfaces/options/modals.ts +18 -13
  225. package/src/interfaces/options/system.ts +6 -3
  226. package/src/interfaces/options/topBar.ts +22 -9
@@ -16,7 +16,11 @@
16
16
  // How long the new top of a `setStackRoot` waits for its first content when `waitForRender` is not set.
17
17
  static const NSTimeInterval ANNStackRootRenderLimit = 0.5;
18
18
 
19
- // The limits of the bar components of a push: title and background 1 s, buttons 0.5 s, together from the wait's start.
19
+ // How long a push waits for its new top's first content when `waitForRender` is not set.
20
+ static const NSTimeInterval ANNPushRenderLimit = 0.5;
21
+
22
+ // The limits of the bar components of a push: title and background 1 s, buttons 0.5 s. With `waitForRender` unset they
23
+ // count from the wait's start, the content's time coming off them; with `true`, from the content's end.
20
24
  static const ANNBarWaitLimits ANNPushBarLimits = {1, 0.5};
21
25
 
22
26
  // The bar components of a `setStackRoot` within its 0.5 s.
@@ -53,8 +57,8 @@ typedef NS_ENUM(NSInteger, ANNStackOperationKind) {
53
57
  @property(nonatomic) BOOL animated;
54
58
  @property(nonatomic) BOOL waits;
55
59
  @property(nonatomic) BOOL atOnce;
56
- // When the replace's own wait for its new top started (`waitForRender` unset): the custom transition's wait takes what
57
- // is left of the 0.5 s. 0 when it didn't wait so.
60
+ // When the push's or the replace's own wait for its new top started (`waitForRender` unset): the custom transition's
61
+ // wait takes what is left of the 0.5 s. 0 when it didn't wait so.
58
62
  @property(nonatomic) CFTimeInterval waitStarted;
59
63
  @property(nonatomic, copy, nullable) dispatch_block_t completion;
60
64
  @property(nonatomic, copy, nullable) ANNTransitionRejectionBlock rejection;
@@ -371,12 +375,13 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
371
375
 
372
376
  - (void)noneWithValue:(id)value
373
377
  answer:(ANNCommandAnswer *)answer
378
+ what:(NSString *)what
374
379
  componentId:(NSString *)componentId
375
380
  source:(UIViewController *)source {
376
381
  ANNAssertMainQueue();
377
382
  ANNStackOperation *operation = [self operationOfKind:ANNStackOperationNone
378
383
  answer:answer
379
- what:@"pop"
384
+ what:what
380
385
  componentId:componentId
381
386
  source:source];
382
387
  operation.value = value;
@@ -559,41 +564,35 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
559
564
  [self scheduleRunNext];
560
565
  return;
561
566
  }
562
- // `waitForRender: true` waits for the content as long as it takes, then for the bar components (their limits from
563
- // the content's end); unset (also with `enabled: false`), only for the bar components, each kind to its own limit,
564
- // without parking (bar roots render out of a window); `false`, for nothing. The content comes during the
565
- // transition. A stack that isn't seen never waits.
567
+ // `waitForRender: true` waits for the content as long as it takes, then for the bar components, each kind to its own
568
+ // limit from the content's end; unset (also with `enabled: false`), for its first content at most 0.5 s, then for
569
+ // the bar components, each kind to its own limit from the wait's start (as when the push waited for them alone);
570
+ // `false`, for nothing, the content coming during the transition. Either wait's content ends after the calls
571
+ // JavaScript made in the commit that mounted the content (its `useScreenOptions`), content there already too, so
572
+ // that their options are in the bar on the transition's first frame. A native screen ends the wait at its start. A
573
+ // stack that isn't seen never waits.
566
574
  Bool *waitForRender = [node resolveOptionsWithDefault].animations.push.waitForRender;
567
575
  // A push whose caller waited already (a preview, `showInColumn`) waits for nothing here.
568
576
  BOOL onScreen = operation.waits && [_display stackIsOnScreen];
569
577
  __weak ANNStackQueue *weakSelf = self;
570
578
  __weak ANNStackOperation *weakOperation = operation;
571
- if (onScreen && waitForRender.hasValue && waitForRender.get) {
572
- [self waitFor:operation
573
- entry:node
574
- content:ANNRenderWaitContentNode
575
- limit:0
576
- unknownLimit:YES
577
- bar:ANNRenderWaitBarAfterContent
578
- barLimits:ANNPushBarLimits
579
- then:^{
580
- [weakSelf startPushTransition:weakOperation];
581
- }];
582
- return;
583
- }
584
- if (onScreen && !waitForRender.hasValue) {
579
+ if (onScreen && (!waitForRender.hasValue || waitForRender.get)) {
580
+ BOOL bounded = !waitForRender.hasValue;
581
+ if (bounded) {
582
+ operation.waitStarted = CACurrentMediaTime();
583
+ }
585
584
  [self waitFor:operation
586
- entry:node
587
- content:ANNRenderWaitContentNone
588
- limit:0
589
- unknownLimit:NO
590
- bar:ANNRenderWaitBarAfterContent
591
- barLimits:ANNPushBarLimits
592
- then:^{
593
- // UIKit sizes the screen inside the push: it makes its content right before.
594
- [weakOperation.node render];
595
- [weakSelf startPushTransition:weakOperation];
596
- }];
585
+ entry:node
586
+ content:ANNRenderWaitContentNode
587
+ limit:bounded ? ANNPushRenderLimit : 0
588
+ unknownLimit:!bounded
589
+ bar:ANNRenderWaitBarAfterContent
590
+ barLimits:ANNPushBarLimits
591
+ afterJavaScript:YES
592
+ barFromStart:bounded
593
+ then:^{
594
+ [weakSelf startPushTransition:weakOperation];
595
+ }];
597
596
  return;
598
597
  }
599
598
  // UIKit sizes the screen inside the push: it makes its content right before.
@@ -616,10 +615,14 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
616
615
  operation.covered = covered;
617
616
  ANNTransitionPlan *plan = nil;
618
617
  if (animated) {
618
+ // Unset `waitForRender`: the custom transition's wait takes what is left of the push's own 0.5 s.
619
+ NSTimeInterval limit =
620
+ [ANNAnimationRules customWaitLimitAfterWaitStartedAt:operation.waitStarted
621
+ commandLimit:ANNPushRenderLimit];
619
622
  plan = [ANNTransitionPlan stackPlanOfKind:ANNTransitionKindPush
620
623
  animation:push
621
624
  waitForRender:push.waitForRender
622
- waitLimit:ANNCustomTransitionWaitLimit
625
+ waitLimit:limit
623
626
  from:covered
624
627
  to:node];
625
628
  }
@@ -746,6 +749,8 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
746
749
  unknownLimit:NO
747
750
  bar:ANNRenderWaitBarWithContent
748
751
  barLimits:ANNStackRootBarLimits
752
+ afterJavaScript:NO
753
+ barFromStart:NO
749
754
  then:^{
750
755
  [weakSelf startReplaceTransition:weakOperation];
751
756
  }];
@@ -759,6 +764,8 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
759
764
  unknownLimit:YES
760
765
  bar:ANNRenderWaitBarAfterContent
761
766
  barLimits:ANNStackRootBarLimits
767
+ afterJavaScript:NO
768
+ barFromStart:NO
762
769
  then:^{
763
770
  [weakSelf startReplaceTransition:weakOperation];
764
771
  }];
@@ -1046,16 +1053,20 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
1046
1053
  // for it in the stack's view, its appearance driven: the wait ends at the entry's ready handler (set first, then the
1047
1054
  // park and `render`, then the test of 16.1), at `limit` with `markReady`, or with `unknownLimit` for a screen whose
1048
1055
  // rendered state stays unknown after 1 s. Without content, only the bar components, without parking (bar roots render
1049
- // out of a window). A wait that would end does nothing once its operation ended or belongs to an old JavaScript; its
1050
- // end runs protected (the end steps of the operation, `then`).
1056
+ // out of a window). With `afterJavaScript` the content's end waits for the calls JavaScript made by then
1057
+ // (`settlesThroughJavaScript`); with `barFromStart` the bar phase's limits count from the wait's start
1058
+ // (`barLimitsFromStart`). A wait that would end does nothing once its operation ended or belongs to an old
1059
+ // JavaScript; its end runs protected (the end steps of the operation, `then`).
1051
1060
  - (void)waitFor:(ANNStackOperation *)operation
1052
- entry:(UIViewController *)entry
1053
- content:(ANNRenderWaitContent)content
1054
- limit:(NSTimeInterval)limit
1055
- unknownLimit:(BOOL)unknownLimit
1056
- bar:(ANNRenderWaitBar)bar
1057
- barLimits:(ANNBarWaitLimits)barLimits
1058
- then:(dispatch_block_t)then {
1061
+ entry:(UIViewController *)entry
1062
+ content:(ANNRenderWaitContent)content
1063
+ limit:(NSTimeInterval)limit
1064
+ unknownLimit:(BOOL)unknownLimit
1065
+ bar:(ANNRenderWaitBar)bar
1066
+ barLimits:(ANNBarWaitLimits)barLimits
1067
+ afterJavaScript:(BOOL)afterJavaScript
1068
+ barFromStart:(BOOL)barFromStart
1069
+ then:(dispatch_block_t)then {
1059
1070
  __weak ANNStackQueue *weakSelf = self;
1060
1071
  __weak ANNStackOperation *weakOperation = operation;
1061
1072
  // The screens the wait is about are those the entry waits for (a pair: the ones it puts in the window).
@@ -1069,6 +1080,8 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
1069
1080
  bar:bar
1070
1081
  barLimits:barLimits];
1071
1082
  wait.unknownContentRule = unknownLimit;
1083
+ wait.settlesThroughJavaScript = afterJavaScript;
1084
+ wait.barLimitsFromStart = barFromStart;
1072
1085
  wait.canEnd = ^BOOL {
1073
1086
  ANNStackQueue *queue = weakSelf;
1074
1087
  ANNStackOperation *waiting = weakOperation;
@@ -6,9 +6,11 @@ NS_ASSUME_NONNULL_BEGIN
6
6
  /// The commands of a stack.
7
7
  @interface UINavigationController (ANNCommands)
8
8
 
9
- /// `setStackRoot`: `children` in place of the stack's screens, the last one on top, from the screen
10
- /// `fromViewController`.
9
+ /// `showInColumn` into the split view's column `column` that is a stack: `children` in place of the stack's screens, the
10
+ /// last one on top, from the screen `fromViewController`. A navigation controller that isn't a stack of the core
11
+ /// rejects `[ANNErrorTexts columnNotStack:column:]`.
11
12
  - (void)setStackChildren:(NSArray<UIViewController *> *)children
13
+ inColumn:(NSString *)column
12
14
  fromViewController:(nullable UIViewController *)fromViewController
13
15
  animated:(BOOL)animated
14
16
  completion:(ANNTransitionCompletionBlock)completion
@@ -8,6 +8,7 @@
8
8
  // A stack of the core takes the screens through its queue, without a wait. A navigation controller of UIKit's own (the
9
9
  // one UIKit puts around a column that is a screen alone) has no queue: it rejects.
10
10
  - (void)setStackChildren:(NSArray<UIViewController *> *)children
11
+ inColumn:(NSString *)column
11
12
  fromViewController:(UIViewController *)fromViewController
12
13
  animated:(BOOL)animated
13
14
  completion:(ANNTransitionCompletionBlock)completion
@@ -21,9 +22,7 @@
21
22
  return;
22
23
  }
23
24
  NSString *fromId = fromViewController.layoutInfo.componentId ?: @"";
24
- NSString *what = [NSString stringWithFormat:@"set the stack of the screen '%@'", fromId];
25
- NSString *reason = @"its column is not a stack of the navigation";
26
- rejection([ANNErrorTexts couldNot:what reason:reason], nil);
25
+ rejection([ANNErrorTexts columnNotStack:fromId column:column], nil);
27
26
  }
28
27
 
29
28
  @end
@@ -23,6 +23,10 @@ NS_ASSUME_NONNULL_BEGIN
23
23
  /// (default `iconColor`) unless `disableSelectedIconTint`. Nil without any.
24
24
  + (nullable UIImage *)selectedIconOf:(ANNBottomTabOptions *)tab;
25
25
 
26
+ /// The images of the options `iconOf:` and `selectedIconOf:` draw from (`bottomTab.icon`, `bottomTab.selectedIcon`):
27
+ /// each one an SF Symbol doesn't cover. Their sources, not their images: nothing loads.
28
+ + (NSArray<Image *> *)rastersOf:(ANNBottomTabOptions *)tab;
29
+
26
30
  /// `selectedIconColor`, else `iconColor` (`bottomTab.selectedIconColor`); nil without both.
27
31
  + (nullable UIColor *)selectedIconColorOf:(ANNBottomTabOptions *)tab;
28
32
 
@@ -67,6 +67,17 @@ static void ANNSetTitleAttributes(UITabBarItemStateAppearance *state, UIColor *c
67
67
  return icon ? ANNRaster(icon, color) : nil;
68
68
  }
69
69
 
70
+ + (NSArray<Image *> *)rastersOf:(ANNBottomTabOptions *)tab {
71
+ NSMutableArray<Image *> *rasters = [NSMutableArray new];
72
+ if (!ANNSymbol(tab.sfSymbol) && tab.icon.source) {
73
+ [rasters addObject:tab.icon];
74
+ }
75
+ if (!ANNSymbol(tab.sfSelectedSymbol) && tab.selectedIcon.source) {
76
+ [rasters addObject:tab.selectedIcon];
77
+ }
78
+ return rasters;
79
+ }
80
+
70
81
  + (UIColor *)selectedIconColorOf:(ANNBottomTabOptions *)tab {
71
82
  return [tab.selectedIconColor withDefault:nil] ?: [tab.iconColor withDefault:nil];
72
83
  }
@@ -817,7 +817,7 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
817
817
  if (index >= _tabs.count || self.nodeHelper.destroyed) {
818
818
  return NO;
819
819
  }
820
- [self sendPendingSelected];
820
+ [self finishEarlierSelection];
821
821
  _pressedIndex = NSNotFound;
822
822
  // The release of a long press: UIKit would select the tab under the finger.
823
823
  if (source == ANNTabPressSourceBar && index == _longPressedIndex) {
@@ -957,6 +957,14 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
957
957
  [self sendPendingSelected];
958
958
  }
959
959
 
960
+ // Before anything newer on these tabs (a press, a long press, a More pick, a choice from code): a switch still running
961
+ // ends with its appearances and schedules its `Selected`, and a `Selected` still waiting goes, as Android's
962
+ // `flushPendingSelected` does.
963
+ - (void)finishEarlierSelection {
964
+ [self endSwitch];
965
+ [self sendPendingSelected];
966
+ }
967
+
960
968
  - (void)sendPendingSelected {
961
969
  if (!_hasPendingSelected) {
962
970
  return;
@@ -980,8 +988,8 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
980
988
  if (index >= _tabs.count || self.nodeHelper.destroyed) {
981
989
  return;
982
990
  }
983
- // A pending `Selected` goes before the event of a later press.
984
- [self sendPendingSelected];
991
+ // A running switch and a pending `Selected` go before the event of a later press.
992
+ [self finishEarlierSelection];
985
993
  if (marksSelection) {
986
994
  _longPressedIndex = index;
987
995
  }
@@ -1024,7 +1032,7 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
1024
1032
  // first, then UIKit's, then the state at once.
1025
1033
  - (void)selectTabFromCode:(NSUInteger)index {
1026
1034
  ANNAssertMainQueue();
1027
- [self sendPendingSelected];
1035
+ [self finishEarlierSelection];
1028
1036
  _pressedIndex = NSNotFound;
1029
1037
  if (index == _selectedTabIndex || index >= _tabs.count || self.nodeHelper.destroyed) {
1030
1038
  return;
@@ -1235,7 +1243,7 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
1235
1243
  if (pick && index == _selectedTabIndex) {
1236
1244
  return;
1237
1245
  }
1238
- [self sendPendingSelected];
1246
+ [self finishEarlierSelection];
1239
1247
  _pressedIndex = NSNotFound;
1240
1248
  if (pick) {
1241
1249
  [self sendPressedOfTabAtIndex:index wasSelected:NO];
@@ -10,8 +10,8 @@ NS_ASSUME_NONNULL_BEGIN
10
10
  /// `ANNBottomTabsController`.
11
11
  @interface ANNBottomTabsPresenter : ANNNodePresenter
12
12
 
13
- /// The repaint of a node the repaint pass left (a tab not in a window): its subtree, children first, with their
14
- /// resolved options.
13
+ /// The repaint of a node the repaint pass left (a tab not in a window), as it is about to show: the nodes of its
14
+ /// subtree whose views are in its view (what shows with it), children first, with their resolved options.
15
15
  + (void)repaintNode:(UIViewController *)node;
16
16
 
17
17
  #pragma mark - Creation, in this order
@@ -3,6 +3,7 @@
3
3
  #import "ANNBottomTabsAccessoryView.h"
4
4
  #import "ANNBottomTabsController.h"
5
5
  #import "ANNBottomTabsCustomRow.h"
6
+ #import "ANNOptionImages.h"
6
7
  #import "ANNTabDotView.h"
7
8
  #import "ANNTabsDesign.h"
8
9
  #import "UITabBar+utils.h"
@@ -73,6 +74,8 @@ static id ANNValue(NSArray *list, NSUInteger index) {
73
74
  NSMutableDictionary<NSString *, ANNBottomTabsAccessoryView *> *_accessoryViews;
74
75
  // The pictures of the bar's colors by color and size.
75
76
  NSMutableDictionary<NSArray *, UIImage *> *_colorImages;
77
+ // The icons an item waits for, by layout index: a token per wait, which a later build of the item replaces.
78
+ NSMutableDictionary<NSNumber *, NSObject *> *_iconLoads;
76
79
  }
77
80
 
78
81
  - (nullable ANNBottomTabsController *)tabs {
@@ -82,10 +85,20 @@ static id ANNValue(NSArray *list, NSUInteger index) {
82
85
  }
83
86
 
84
87
  + (void)repaintNode:(UIViewController *)node {
88
+ [self repaintNode:node showingIn:node.viewIfLoaded];
89
+ }
90
+
91
+ // Children first. Only what shows with the tab repaints: a node whose view is out of the tab's views (a stack's lower
92
+ // screens, a hidden tab of tabs inside) takes the new options at its own appearance, as the repaint of what shows
93
+ // leaves the nodes out of a window (`ANNOptionsRepaint`).
94
+ + (void)repaintNode:(UIViewController *)node showingIn:(nullable UIView *)tabView {
85
95
  for (UIViewController *child in node.layoutChildren) {
86
- [self repaintNode:child];
96
+ [self repaintNode:child showingIn:tabView];
97
+ }
98
+ UIView *view = node.viewIfLoaded;
99
+ if (tabView && view && [view isDescendantOfView:tabView]) {
100
+ [node.presenter applyDefaultOptionsChange:[node resolveOptionsWithDefault]];
87
101
  }
88
- [node.presenter applyDefaultOptionsChange:[node resolveOptionsWithDefault]];
89
102
  }
90
103
 
91
104
  #pragma mark - Creation
@@ -112,6 +125,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
112
125
  _buttonIds = [NSMapTable weakToStrongObjectsMapTable];
113
126
  _accessoryViews = [NSMutableDictionary new];
114
127
  _colorImages = [NSMutableDictionary new];
128
+ _iconLoads = [NSMutableDictionary new];
115
129
  for (NSUInteger index = 0; index < count; index++) {
116
130
  [_badges addObject:NSNull.null];
117
131
  [_dots addObject:[ANNBottomTabDotIndicatorOptions new]];
@@ -326,8 +340,12 @@ static id ANNValue(NSArray *list, NSUInteger index) {
326
340
  // The row's items show nothing: no image, no title, only the accessibility.
327
341
  NSString *text = [ANNBottomTabItemBuilder titleOf:bottomTab];
328
342
  NSString *title = row || _titlesHidden ? nil : text;
329
- UIImage *icon = row ? nil : [ANNBottomTabItemBuilder iconOf:bottomTab];
330
- UIImage *selectedIcon = row ? nil : [ANNBottomTabItemBuilder selectedIconOf:bottomTab];
343
+ // While new icons load off the main queue the item keeps the ones it shows; a wait of an earlier build is over.
344
+ [_iconLoads removeObjectForKey:@(index)];
345
+ BOOL keepsIcons = !row && [self loadsIconsLaterOf:bottomTab atIndex:index];
346
+ UIImage *icon = row || keepsIcons ? nil : [ANNBottomTabItemBuilder iconOf:bottomTab];
347
+ UIImage *selectedIcon =
348
+ row || keepsIcons ? nil : [ANNBottomTabItemBuilder selectedIconOf:bottomTab];
331
349
  if (@available(iOS 18.0, *)) {
332
350
  if (uiTab) {
333
351
  UITab *systemTab = uiTab;
@@ -339,12 +357,13 @@ static id ANNValue(NSArray *list, NSUInteger index) {
339
357
  systemTab.title = tabTitle;
340
358
  }
341
359
  }
342
- if ((!search || icon || row) && systemTab.image != icon) {
360
+ if (!keepsIcons && (!search || icon || row) && systemTab.image != icon) {
343
361
  systemTab.image = icon;
344
362
  }
345
363
  #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 270000
346
364
  if (@available(iOS 26.1, *)) {
347
- if ((!search || selectedIcon || row) && systemTab.selectedImage != selectedIcon) {
365
+ if (!keepsIcons && (!search || selectedIcon || row) &&
366
+ systemTab.selectedImage != selectedIcon) {
348
367
  systemTab.selectedImage = selectedIcon;
349
368
  }
350
369
  }
@@ -365,7 +384,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
365
384
  item.accessibilityLabel = text;
366
385
  }
367
386
  }
368
- if (!roleItem || icon) {
387
+ if (!keepsIcons && (!roleItem || icon)) {
369
388
  if (item.image != icon) {
370
389
  item.image = icon;
371
390
  }
@@ -427,6 +446,89 @@ static id ANNValue(NSArray *list, NSUInteger index) {
427
446
  }
428
447
  }
429
448
 
449
+ // `bottomTab` with the images that came in place of the rasters they came for: a build from it asks the cache for
450
+ // none of them. A raster that failed stays as it was (its failure answers for a moment).
451
+ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray<Image *> *rasters,
452
+ NSArray *came) {
453
+ ANNBottomTabOptions *built = [bottomTab copy];
454
+ for (NSUInteger place = 0; place < rasters.count; place++) {
455
+ UIImage *image = [came[place] isKindOfClass:UIImage.class] ? came[place] : nil;
456
+ if (!image) {
457
+ continue;
458
+ }
459
+ Image *raster = rasters[place];
460
+ if (raster == bottomTab.icon) {
461
+ built.icon = [[Image alloc] initWithValue:image];
462
+ }
463
+ if (raster == bottomTab.selectedIcon) {
464
+ built.selectedIcon = [[Image alloc] initWithValue:image];
465
+ }
466
+ }
467
+ return built;
468
+ }
469
+
470
+ // New raster icons of an item on the bar that shows load off the main queue when the cache doesn't have them: the item
471
+ // takes them when they came, built again from the same options with the images that came (the cache may have let them
472
+ // go by then: icons larger than it), unless it was built again first (the last build wins). The first icons, and every
473
+ // icon while the bar is out of a window, load at once: the tab's first frame shows them.
474
+ - (BOOL)loadsIconsLaterOf:(ANNBottomTabOptions *)bottomTab atIndex:(NSUInteger)index {
475
+ if (!_tabsAreSet || !self.tabs.viewIfLoaded.window) {
476
+ return NO;
477
+ }
478
+ NSArray<Image *> *rasters = [ANNBottomTabItemBuilder rastersOf:bottomTab];
479
+ BOOL later = NO;
480
+ for (Image *raster in rasters) {
481
+ if ([ANNOptionImages loadsLater:raster.source]) {
482
+ later = YES;
483
+ }
484
+ }
485
+ if (!later) {
486
+ return NO;
487
+ }
488
+ NSObject *load = [NSObject new];
489
+ _iconLoads[@(index)] = load;
490
+ // Every raster of the item, those the cache has too: each comes with its image, by its place in `rasters`.
491
+ NSMutableArray *came = [NSMutableArray arrayWithCapacity:rasters.count];
492
+ for (NSUInteger each = 0; each < rasters.count; each++) {
493
+ [came addObject:NSNull.null];
494
+ }
495
+ __block NSUInteger pending = rasters.count;
496
+ __block BOOL asking = YES;
497
+ __weak ANNBottomTabsPresenter *weakSelf = self;
498
+ [rasters enumerateObjectsUsingBlock:^(Image *raster, NSUInteger place, BOOL *stop) {
499
+ [ANNOptionImages loadLater:raster.source
500
+ done:^(UIImage *image) {
501
+ if (image) {
502
+ came[place] = image;
503
+ }
504
+ pending--;
505
+ ANNBottomTabsPresenter *presenter = weakSelf;
506
+ if (pending == 0 && !asking) {
507
+ [presenter iconsCame:load
508
+ atIndex:index
509
+ bottomTab:ANNWithIcons(bottomTab, rasters, came)];
510
+ }
511
+ }];
512
+ }];
513
+ asking = NO;
514
+ if (pending == 0) {
515
+ // A load ended while the others were asked for: the cache has them all now.
516
+ [_iconLoads removeObjectForKey:@(index)];
517
+ return NO;
518
+ }
519
+ return YES;
520
+ }
521
+
522
+ - (void)iconsCame:(NSObject *)load
523
+ atIndex:(NSUInteger)index
524
+ bottomTab:(ANNBottomTabOptions *)bottomTab {
525
+ if (_iconLoads[@(index)] != load || index >= self.tabs.layoutTabs.count) {
526
+ return;
527
+ }
528
+ [_iconLoads removeObjectForKey:@(index)];
529
+ [self buildItemAtIndex:index bottomTab:bottomTab updatesBadgeAndDot:NO];
530
+ }
531
+
430
532
  - (void)updateDotAtIndex:(NSUInteger)index
431
533
  shown:(BOOL)shown
432
534
  options:(ANNBottomTabDotIndicatorOptions *)dot {
@@ -2,13 +2,14 @@
2
2
 
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
5
- /// The one point where options change: a number for the whole process that grows each time a node's own options or the
6
- /// default options change. A node's resolved options are cached with the number they were built at, so any change makes
7
- /// every cache miss. Main queue.
5
+ /// The one point where options change: a number for the whole process that grows each time a node's own options, its
6
+ /// user layer or the default options change. A node's resolved options are cached with the number they were built at,
7
+ /// so any change makes every cache miss. Main queue.
8
8
  ///
9
- /// Exactly two places call `optionsChanged`: the setter of a node's own options (`ANNNodeHelper.options`, which
10
- /// `mergeOwnOptions:` and `changeOwnOptions:` go through) and the store of new default options (`ANNDefaultOptions`).
11
- /// Nothing else may change own options or defaults: a write past them would leave caches stale.
9
+ /// Exactly three places call `optionsChanged`: the setter of a node's own options (`ANNNodeHelper.options`, which
10
+ /// `mergeOwnOptions:` and `changeOwnOptions:` go through), the setter of its user layer
11
+ /// (`ANNNodeHelper setUserChoice:forKey:`) and the store of new default options (`ANNDefaultOptions`). Nothing else may
12
+ /// change own options, choices or defaults: a write past them would leave caches stale.
12
13
  @interface ANNOptionsCache : NSObject
13
14
 
14
15
  /// The generation of the options now.
@@ -15,9 +15,10 @@ typedef NS_ENUM(NSInteger, SearchBarPlacement) {
15
15
  SearchBarPlacementIntegratedCentered,
16
16
  };
17
17
 
18
- /// `topBar.searchBar` (`OptionsSearchBar`): the search of a screen. `ANNSearchPresenter` applies it. The scope, the
19
- /// tokens and the suggestions are written back by the search as the user changes them (`changeOwnOptions:`), so they
20
- /// stay when the options apply again.
18
+ /// `topBar.searchBar` (`OptionsSearchBar`): the search of a screen. `ANNSearchPresenter` applies it. The scope the user
19
+ /// picks, the tokens the user deletes or brings back and the suggestions UIKit removes go into the screen's user layer
20
+ /// (`setUserChoice:forKey:`), not into these options: they stay when the options apply again and through a
21
+ /// `mergeOptions` that sends the same values, and give way when a merge changes the field.
21
22
  @interface ANNSearchBarOptions : ANNOptions
22
23
 
23
24
  @property(nonatomic, strong) Bool *visible;
@@ -0,0 +1,57 @@
1
+ #import "ANNNavigationOptions.h"
2
+ #import <Foundation/Foundation.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ /// Writes a choice into options being resolved: `options` is a copy the block may change, but its lists and their
7
+ /// items are shared with other trees, so the block puts in a new list with a copy of what it changes, never a change in
8
+ /// place. It finds what it changes by identity (a button by its id, a menu item by its id, a scope by its id) and
9
+ /// leaves the options as they are when that is not there.
10
+ typedef void (^ANNUserChoiceApply)(ANNNavigationOptions *options, id value);
11
+
12
+ /// The value of a choice after the app's `mergeOptions` into the node, by Android's rules (`UserChoices.yieldTo`):
13
+ /// `merge` is what the app sent, `own` the node's own options before it, `before` and `after` the node's resolution
14
+ /// with the defaults and without any user layer (the app's options alone) before and after the merge. The same `value`
15
+ /// keeps the choice, a smaller value keeps part of it, nil drops it. A part leaves when what it was made in is gone
16
+ /// from `after` (a button, a menu item, a scope), or when `merge` gives its field a value other than the one the app
17
+ /// had there: `own`'s, or `before`'s where `own` has no such field (a list the node takes from its stack or the
18
+ /// defaults). The same value sent again (a re-render) keeps it.
19
+ typedef id _Nullable (^ANNUserChoiceMerged)(id value, ANNNavigationOptions *merge,
20
+ ANNNavigationOptions *own,
21
+ ANNNavigationOptions *before,
22
+ ANNNavigationOptions *after);
23
+
24
+ /// One choice the user made in native UI (a toggled button, the check of a single-selection menu, the search scope, the
25
+ /// tokens and the suggestions UIKit removed, a renamed title), kept in the node's user layer
26
+ /// (`ANNNodeHelper setUserChoice:forKey:`) apart from its own options. The node's resolution writes it over the own
27
+ /// options (`applyTo:`); the app's real change of the same field, or a merge after which what it is about is gone,
28
+ /// takes it away (`choiceAfterMerge:own:before:after:`). The blocks capture only plain data (a side, an id, a key),
29
+ /// never a presenter: the node keeps them as long as the choice.
30
+ @interface ANNUserChoice : NSObject
31
+
32
+ /// What the user chose, never changed: a number, a string, an array of ids or of option copies.
33
+ @property(nonatomic, strong, readonly) id value;
34
+
35
+ + (instancetype)choiceWithValue:(id)value
36
+ apply:(ANNUserChoiceApply)apply
37
+ merged:(ANNUserChoiceMerged)merged;
38
+
39
+ - (instancetype)init NS_UNAVAILABLE;
40
+ + (instancetype)new NS_UNAVAILABLE;
41
+
42
+ /// The same choice with another value.
43
+ - (instancetype)choiceWithValue:(id)value;
44
+
45
+ /// The choice written into `options` (a copy being resolved).
46
+ - (void)applyTo:(ANNNavigationOptions *)options;
47
+
48
+ /// The choice after the app's `mergeOptions` (`ANNUserChoiceMerged`): itself, the choice with a smaller value, or nil
49
+ /// when it goes.
50
+ - (nullable ANNUserChoice *)choiceAfterMerge:(ANNNavigationOptions *)merge
51
+ own:(ANNNavigationOptions *)own
52
+ before:(ANNNavigationOptions *)before
53
+ after:(ANNNavigationOptions *)after;
54
+
55
+ @end
56
+
57
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,44 @@
1
+ #import "ANNUserChoice.h"
2
+
3
+ @implementation ANNUserChoice {
4
+ ANNUserChoiceApply _apply;
5
+ ANNUserChoiceMerged _merged;
6
+ }
7
+
8
+ + (instancetype)choiceWithValue:(id)value
9
+ apply:(ANNUserChoiceApply)apply
10
+ merged:(ANNUserChoiceMerged)merged {
11
+ return [[self alloc] initWithValue:value apply:apply merged:merged];
12
+ }
13
+
14
+ - (instancetype)initWithValue:(id)value
15
+ apply:(ANNUserChoiceApply)apply
16
+ merged:(ANNUserChoiceMerged)merged {
17
+ if (self = [super init]) {
18
+ _value = value;
19
+ _apply = [apply copy];
20
+ _merged = [merged copy];
21
+ }
22
+ return self;
23
+ }
24
+
25
+ - (instancetype)choiceWithValue:(id)value {
26
+ return [[ANNUserChoice alloc] initWithValue:value apply:_apply merged:_merged];
27
+ }
28
+
29
+ - (void)applyTo:(ANNNavigationOptions *)options {
30
+ _apply(options, _value);
31
+ }
32
+
33
+ - (ANNUserChoice *)choiceAfterMerge:(ANNNavigationOptions *)merge
34
+ own:(ANNNavigationOptions *)own
35
+ before:(ANNNavigationOptions *)before
36
+ after:(ANNNavigationOptions *)after {
37
+ id value = _merged(_value, merge, own, before, after);
38
+ if (!value) {
39
+ return nil;
40
+ }
41
+ return value == _value ? self : [self choiceWithValue:value];
42
+ }
43
+
44
+ @end