@apexnative/navigation 1.0.0-alpha.7 → 1.0.0-alpha.9
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.
- package/CHANGELOG.md +21 -0
- package/android/src/main/java/dev/apexnative/navigation/options/SideMenuOptions.kt +6 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt +35 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +50 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarVisibility.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/sidemenu/SideMenuView.kt +12 -3
- package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +34 -4
- package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +6 -3
- package/docs-md/containers/side-menu.md +15 -0
- package/docs-md/containers/tabs.md +1 -1
- package/docs-md/index.json +4 -0
- package/ios/CLAUDE.md +8 -2
- package/ios/CORE.md +2 -2
- package/ios/Core/ANNLayoutProtocol.h +5 -5
- package/ios/Core/ANNParking.h +5 -4
- package/ios/Core/ANNReloadCoordinator.mm +1 -4
- package/ios/Core/ANNRenderWait.h +5 -5
- package/ios/Core/ANNRenderWait.mm +24 -39
- package/ios/Core/ANNUtils.h +7 -0
- package/ios/Core/ANNUtils.mm +10 -0
- package/ios/Core/ApexNavigation.mm +1 -4
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +1 -1
- package/ios/Layouts/Overlay/ANNOverlayCoordinator.mm +2 -8
- package/ios/Layouts/Root/ANNRootCoordinator.mm +1 -1
- package/ios/Layouts/Screen/ANNComponentViewController.h +9 -15
- package/ios/Layouts/Screen/ANNComponentViewController.mm +6 -42
- package/ios/Layouts/Screen/ANNScreenPlace.h +12 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +46 -16
- package/ios/Layouts/SideMenu/ANNSideMenuTypes.h +8 -0
- package/ios/Layouts/SideMenu/ANNSideMenuViewController.mm +83 -14
- package/ios/Layouts/Stack/ANNStackController.mm +5 -2
- package/ios/Layouts/Stack/ANNStackQueue.h +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsController.h +3 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +222 -51
- package/ios/Options/ANNSideMenuOptions.h +2 -0
- package/ios/Options/ANNSideMenuOptions.mm +2 -0
- package/ios/React/ANNSafeAreaViewComponentView.mm +2 -2
- package/ios/React/Slots/ANNSlotHost.mm +1 -3
- package/lib/module/api/screens/registerScreens.js +26 -5
- package/lib/module/api/screens/registerScreens.js.map +1 -1
- package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/sideMenu.d.ts +16 -0
- package/lib/typescript/interfaces/options/sideMenu.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/screens/registerScreens.tsx +17 -2
- package/src/interfaces/options/bottomTabs.ts +2 -1
- package/src/interfaces/options/sideMenu.ts +17 -0
- package/ios/Layouts/Tabs/ANNTabsPreloadWindow.h +0 -27
- package/ios/Layouts/Tabs/ANNTabsPreloadWindow.mm +0 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0-alpha.9](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.8...v1.0.0-alpha.9) (2026-10-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* side menus can be created after the center or at their first open ([#67](https://github.com/apexnative/navigation/issues/67)) ([a4a0ecf](https://github.com/apexnative/navigation/commit/a4a0ecfdc04ccd13d2c96b5799b45e510113a7e8))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **android:** the room beside a moving screen goes on with what its edge shows ([#64](https://github.com/apexnative/navigation/issues/64)) ([eb0936f](https://github.com/apexnative/navigation/commit/eb0936f15d8d157d31621917920814df2c725ff1))
|
|
14
|
+
* **ios:** tabs not shown yet render their screens before they show ([#68](https://github.com/apexnative/navigation/issues/68)) ([cf34a09](https://github.com/apexnative/navigation/commit/cf34a09a772865a8e3ff377b053c968e306f4972))
|
|
15
|
+
|
|
16
|
+
## [1.0.0-alpha.8](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.7...v1.0.0-alpha.8) (2026-10-29)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* group options reach the screens that show, and the android bar hides as it shows ([#63](https://github.com/apexnative/navigation/issues/63)) ([474ca8a](https://github.com/apexnative/navigation/commit/474ca8aae53e5284d64a5d90dd852fdbdcbabcac))
|
|
22
|
+
* **ios:** overlays and preloaded tabs take the scheme the app set with Appearance.setColorScheme ([#61](https://github.com/apexnative/navigation/issues/61)) ([232d7e0](https://github.com/apexnative/navigation/commit/232d7e038b53cd9656e59c7cfa5cbf0f52f9b587))
|
|
23
|
+
|
|
3
24
|
## [1.0.0-alpha.7](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.6...v1.0.0-alpha.7) (2026-10-29)
|
|
4
25
|
|
|
5
26
|
|
|
@@ -20,16 +20,18 @@ class SideMenuOptions {
|
|
|
20
20
|
@JvmField var right = SideMenuSideOptions()
|
|
21
21
|
@JvmField var openGestureMode: Text = NullText()
|
|
22
22
|
@JvmField var animationType: Text = NullText()
|
|
23
|
+
@JvmField var menusAttachMode: Text = NullText()
|
|
23
24
|
@JvmField var testID: Text = NullText()
|
|
24
25
|
|
|
25
26
|
fun hasValue(): Boolean = left.hasValue() || right.hasValue() || openGestureMode.hasValue() ||
|
|
26
|
-
animationType.hasValue() || testID.hasValue()
|
|
27
|
+
animationType.hasValue() || menusAttachMode.hasValue() || testID.hasValue()
|
|
27
28
|
|
|
28
29
|
fun mergeWith(other: SideMenuOptions) {
|
|
29
30
|
left.mergeWith(other.left)
|
|
30
31
|
right.mergeWith(other.right)
|
|
31
32
|
if (other.openGestureMode.hasValue()) openGestureMode = other.openGestureMode
|
|
32
33
|
if (other.animationType.hasValue()) animationType = other.animationType
|
|
34
|
+
if (other.menusAttachMode.hasValue()) menusAttachMode = other.menusAttachMode
|
|
33
35
|
if (other.testID.hasValue()) testID = other.testID
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -38,6 +40,7 @@ class SideMenuOptions {
|
|
|
38
40
|
right.mergeWithDefault(defaults.right)
|
|
39
41
|
if (!openGestureMode.hasValue()) openGestureMode = defaults.openGestureMode
|
|
40
42
|
if (!animationType.hasValue()) animationType = defaults.animationType
|
|
43
|
+
if (!menusAttachMode.hasValue()) menusAttachMode = defaults.menusAttachMode
|
|
41
44
|
if (!testID.hasValue()) testID = defaults.testID
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -46,6 +49,7 @@ class SideMenuOptions {
|
|
|
46
49
|
companion object {
|
|
47
50
|
private val OPEN_GESTURE_MODES = setOf("bezel", "entireScreen")
|
|
48
51
|
private val ANIMATION_TYPES = setOf("parallax", "slide", "slide-and-scale", "door")
|
|
52
|
+
private val MENUS_ATTACH_MODES = setOf("together", "afterCenter", "onOpen")
|
|
49
53
|
|
|
50
54
|
@JvmStatic
|
|
51
55
|
fun parse(map: ReadableMap?): SideMenuOptions {
|
|
@@ -55,6 +59,7 @@ class SideMenuOptions {
|
|
|
55
59
|
options.right = map.group("right") { SideMenuSideOptions.parse(it) }
|
|
56
60
|
options.openGestureMode = TextParser.parse(map, "openGestureMode", OPEN_GESTURE_MODES)
|
|
57
61
|
options.animationType = TextParser.parse(map, "animationType", ANIMATION_TYPES)
|
|
62
|
+
options.menusAttachMode = TextParser.parse(map, "menusAttachMode", MENUS_ATTACH_MODES)
|
|
58
63
|
options.testID = TextParser.parse(map, "testID")
|
|
59
64
|
return options
|
|
60
65
|
}
|
|
@@ -29,7 +29,8 @@ import dev.apexnative.navigation.views.sidemenu.SideMenuView
|
|
|
29
29
|
* - The current child is the open menu's node, else the center ([SideMenuMotion.openSide]: from the end of the
|
|
30
30
|
* opening to the end of the closing); the state, the options and back follow it.
|
|
31
31
|
* - The center and the open menu show with the side menu ([visibleChildren]); a wait for the side menu to render
|
|
32
|
-
* waits for the center ([waitedChildren]); the menus' views
|
|
32
|
+
* waits for the center ([waitedChildren]); the menus' views join the drawer as `sideMenu.menusAttachMode` says
|
|
33
|
+
* ([attached]): with the side menu's view, once it first appeared, or each as it first starts to show.
|
|
33
34
|
* - Its options ([SideMenuPresenter]): from the resolution of [resolveShownOptions] at creation, at every appearance
|
|
34
35
|
* of a screen inside, and for the fields a merge names; `sideMenu.<side>.visible` acts once, from a merge or from the
|
|
35
36
|
* layout at the first show. What goes on up goes without `sideMenu`: the nearest side menu acts.
|
|
@@ -64,6 +65,12 @@ internal class SideMenuController(
|
|
|
64
65
|
// From the end of its appearance's start until it disappears: the drawer's motion tells the menu screens then.
|
|
65
66
|
private var appeared = false
|
|
66
67
|
private var firstShown = false
|
|
68
|
+
private var firstAppeared = false
|
|
69
|
+
|
|
70
|
+
private val attachMode = SideMenuRules.attachModeOf(initialOptions.sideMenu, DefaultOptions.current.sideMenu)
|
|
71
|
+
|
|
72
|
+
/** The menus whose nodes' views are in the drawer, or join it as it is made. */
|
|
73
|
+
internal val attached = HashSet<Side>(2)
|
|
67
74
|
// Its children go first as it is destroyed: nothing reacts to them, nor to the drawer, from the start.
|
|
68
75
|
private var destroying = false
|
|
69
76
|
|
|
@@ -72,6 +79,7 @@ internal class SideMenuController(
|
|
|
72
79
|
|
|
73
80
|
init {
|
|
74
81
|
getChildControllers().forEach { it.parentController = this }
|
|
82
|
+
SideMenuRules.attachedAtCreation(attachMode).filter { childOf(it) != null }.forEach { attached += it }
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
override fun getChildControllers(): List<ViewController<*>> = listOfNotNull(center, left, right)
|
|
@@ -88,13 +96,16 @@ internal class SideMenuController(
|
|
|
88
96
|
|
|
89
97
|
private fun childOf(side: Side): ViewController<*>? = if (side == Side.Left) left else right
|
|
90
98
|
|
|
91
|
-
// The drawer and its parts, each node
|
|
92
|
-
// view existed is open in it from the start; the drawer is silent before its first layout.
|
|
99
|
+
// The drawer and its parts, the view of each node attached so far in its part; the creation's options. An open menu
|
|
100
|
+
// recorded before the view existed is open in it from the start; the drawer is silent before its first layout.
|
|
93
101
|
override fun createView(): SideMenuView {
|
|
94
102
|
val view = SideMenuView(activity)
|
|
95
103
|
view.setCenter(center.view)
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
for (side in Side.entries) {
|
|
105
|
+
val child = childOf(side) ?: continue
|
|
106
|
+
view.addPart(side)
|
|
107
|
+
if (side in attached) view.fillPart(side, child.view)
|
|
108
|
+
}
|
|
98
109
|
menus.applyOnCreation(view, resolveShownOptions(DefaultOptions.current))
|
|
99
110
|
motion.openSide?.let { view.open(it, false) }
|
|
100
111
|
view.follow(motion)
|
|
@@ -172,6 +183,14 @@ internal class SideMenuController(
|
|
|
172
183
|
motion.containerAppeared()
|
|
173
184
|
}
|
|
174
185
|
|
|
186
|
+
// `afterCenter`: the menus join once the side menu first appeared with its center.
|
|
187
|
+
override fun onViewDidAppear() {
|
|
188
|
+
super.onViewDidAppear()
|
|
189
|
+
if (firstAppeared || destroying || isDestroyed) return
|
|
190
|
+
firstAppeared = true
|
|
191
|
+
SideMenuRules.attachedAfterAppearance(attachMode).forEach(::attach)
|
|
192
|
+
}
|
|
193
|
+
|
|
175
194
|
override fun onViewDisappear() {
|
|
176
195
|
appeared = false
|
|
177
196
|
motion.containerDisappeared()
|
|
@@ -222,6 +241,16 @@ internal class SideMenuController(
|
|
|
222
241
|
if (child === currentChild) super.tabBarSettled(child)
|
|
223
242
|
}
|
|
224
243
|
|
|
244
|
+
// The view of the menu on [side] joins its part, once, before its screens hear that they appear: a stack lays out
|
|
245
|
+
// its bar as it appears in the window. The drawer's first movement may come while it draws (a peek settles in
|
|
246
|
+
// `computeScroll`, before the drawer draws its children): the view joins the part then, measured and laid out at the
|
|
247
|
+
// next frame.
|
|
248
|
+
private fun attach(side: Side) {
|
|
249
|
+
val child = childOf(side) ?: return
|
|
250
|
+
if (destroying || isDestroyed || !attached.add(side)) return
|
|
251
|
+
peekView()?.fillPart(side, child.view)
|
|
252
|
+
}
|
|
253
|
+
|
|
225
254
|
/** Destroys the side menu once: the drawer's callbacks stop first, then the children go. */
|
|
226
255
|
override fun destroy() {
|
|
227
256
|
if (isDestroyed) return
|
|
@@ -238,6 +267,8 @@ internal class SideMenuController(
|
|
|
238
267
|
|
|
239
268
|
override fun child(side: Side): ViewController<*>? = childOf(side)
|
|
240
269
|
|
|
270
|
+
override fun starts(side: Side) = attach(side)
|
|
271
|
+
|
|
241
272
|
override fun isEnabled(side: Side): Boolean =
|
|
242
273
|
SideMenuRules.isEnabled(resolveShownOptions(DefaultOptions.current).sideMenu, side)
|
|
243
274
|
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt
CHANGED
|
@@ -82,6 +82,12 @@ internal class SideMenuMotion(
|
|
|
82
82
|
/** `sideMenu.<side>.enabled` of the side menu's options now. */
|
|
83
83
|
fun isEnabled(side: Side): Boolean
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The menu on [side] starts to show: the drawer's first movement, or a command that opens it. Before its
|
|
87
|
+
* screen hears that it appears.
|
|
88
|
+
*/
|
|
89
|
+
fun starts(side: Side)
|
|
90
|
+
|
|
85
91
|
/** `visible: true` for a side the layout doesn't have: a warning, nothing else. */
|
|
86
92
|
fun missingSide(side: Side)
|
|
87
93
|
|
|
@@ -122,6 +128,7 @@ internal class SideMenuMotion(
|
|
|
122
128
|
fun slid(side: Side, offset: Float) {
|
|
123
129
|
// Not more than 0 also takes a NaN: `DrawerLayout` divides by the drawer's width, which may be 0.
|
|
124
130
|
if (destroyed || !(offset > 0f)) return
|
|
131
|
+
if (!isOnScreen(side)) host.starts(side)
|
|
125
132
|
onScreen += side
|
|
126
133
|
if (side in appearing || !host.appeared) return
|
|
127
134
|
appearing += side
|
|
@@ -207,6 +214,7 @@ internal class SideMenuMotion(
|
|
|
207
214
|
}
|
|
208
215
|
// Never decided by where the other menu is: a side asked to open in the same merge hasn't moved yet.
|
|
209
216
|
if (drawer.has(side.other)) move(side.other, open = false, animate = animated)
|
|
217
|
+
if (!isOnScreen(side)) host.starts(side)
|
|
210
218
|
onScreen += side
|
|
211
219
|
// A locked menu opened from code can be dragged closed while it is open.
|
|
212
220
|
drawer.lock(side, locked = false)
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt
CHANGED
|
@@ -11,11 +11,23 @@ import kotlin.math.min
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* What the options of a side menu do on Android (`sideMenu.*`), without views: the values of each moment, `visible` as
|
|
14
|
-
* a command, the sizes, the `testID`s. Android reads `enabled`, `width`, `height`,
|
|
15
|
-
* `testID`s of `sideMenu`; `openGestureMode`, `animationType`,
|
|
16
|
-
* and `glass` are iOS's.
|
|
14
|
+
* a command, the sizes, the `testID`s, when the menus join the drawer. Android reads `enabled`, `width`, `height`,
|
|
15
|
+
* `visible`, `animate`, `menusAttachMode` and the `testID`s of `sideMenu`; `openGestureMode`, `animationType`,
|
|
16
|
+
* `openMode`, `shouldStretchDrawer`, `animationVelocity` and `glass` are iOS's.
|
|
17
17
|
*/
|
|
18
18
|
internal object SideMenuRules {
|
|
19
|
+
/** When the views of the menus' nodes join the drawer (`sideMenu.menusAttachMode`): their content renders then. */
|
|
20
|
+
enum class AttachMode {
|
|
21
|
+
/** With the side menu's view. */
|
|
22
|
+
Together,
|
|
23
|
+
|
|
24
|
+
/** Once the side menu first appeared, with its center. */
|
|
25
|
+
AfterCenter,
|
|
26
|
+
|
|
27
|
+
/** Each as it first starts to show: the drawer's first movement, or a command that opens it. */
|
|
28
|
+
OnOpen,
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
/** A side's `visible` of one merge: open or close it, with its `animate`. */
|
|
20
32
|
data class Command(val side: Side, val visible: Boolean, val animate: Boolean)
|
|
21
33
|
|
|
@@ -43,6 +55,26 @@ internal object SideMenuRules {
|
|
|
43
55
|
*/
|
|
44
56
|
fun firstShow(layout: SideMenuOptions): List<Side> = Side.entries.filter { sideOf(layout, it).visible.isTrue }
|
|
45
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The mode of `sideMenu.menusAttachMode`: the side menu's [own] options, else the [defaults]; `together` without
|
|
60
|
+
* either. Read once, as the side menu is made.
|
|
61
|
+
*/
|
|
62
|
+
fun attachModeOf(own: SideMenuOptions, defaults: SideMenuOptions): AttachMode {
|
|
63
|
+
val value = own.menusAttachMode.takeIf { it.hasValue() } ?: defaults.menusAttachMode
|
|
64
|
+
return when (value.get(null)) {
|
|
65
|
+
"afterCenter" -> AttachMode.AfterCenter
|
|
66
|
+
"onOpen" -> AttachMode.OnOpen
|
|
67
|
+
else -> AttachMode.Together
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The menus that join as the side menu is made: both with `together`. */
|
|
72
|
+
fun attachedAtCreation(mode: AttachMode): List<Side> = if (mode == AttachMode.Together) Side.entries else emptyList()
|
|
73
|
+
|
|
74
|
+
/** The menus that join once the side menu first appeared: both with `afterCenter`. */
|
|
75
|
+
fun attachedAfterAppearance(mode: AttachMode): List<Side> =
|
|
76
|
+
if (mode == AttachMode.AfterCenter) Side.entries else emptyList()
|
|
77
|
+
|
|
46
78
|
/** `enabled` of [side]: true unless `false`, from the side menu's resolved options. */
|
|
47
79
|
fun isEnabled(options: SideMenuOptions, side: Side): Boolean = !sideOf(options, side).enabled.isFalse
|
|
48
80
|
|
|
@@ -129,6 +129,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
129
129
|
// The bar goes with the screens of a transition of the stack's own, and the screen that leaves with its picture.
|
|
130
130
|
private var travelling = false
|
|
131
131
|
private var travelLeaving: ViewController<*>? = null
|
|
132
|
+
// The bar hides as it shows now, and the options of the screen it hides for wait until it is gone.
|
|
133
|
+
private var waitsForHide = false
|
|
132
134
|
|
|
133
135
|
private enum class Part { Look, Title, Left, Right, Back, LargeTitle, Visible, TestId }
|
|
134
136
|
|
|
@@ -141,7 +143,7 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
141
143
|
this.bar = bar
|
|
142
144
|
leftButtons = ButtonBar(bar.leftButtonBar, ::loadIcon, Buttons())
|
|
143
145
|
rightButtons = ButtonBar(bar.rightButtonBar, ::loadIcon, Buttons())
|
|
144
|
-
visibility = TopBarVisibility(VisibilityTarget(bar) { collapse?.reset() })
|
|
146
|
+
visibility = TopBarVisibility(VisibilityTarget(bar) { collapse?.reset() }, ::hidden)
|
|
145
147
|
rowTransition = BarRowTransition(RowTarget(bar).also { rowTarget = it })
|
|
146
148
|
collapse = TopBarCollapseBehavior(bar)
|
|
147
149
|
bar.leftButtonBar.setNavigationOnClickListener { host.backPressed() }
|
|
@@ -182,6 +184,7 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
182
184
|
val bar = bar
|
|
183
185
|
val leaving = shownEntry
|
|
184
186
|
val lowers = bar != null && bar.isLargeTitleShown && incoming != null && leaving != null &&
|
|
187
|
+
!hidesMoving(host.resolvedOf(incoming), visibility, travel != null) &&
|
|
185
188
|
lowersBar(motion, incoming, host.resolvedOf(incoming))
|
|
186
189
|
rowTarget?.withLargeTitle = !lowers
|
|
187
190
|
if (lowers) pictureUnderRow(bar, leaving)
|
|
@@ -190,6 +193,15 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
190
193
|
if (travel != null && bar != null && incoming != null) beginTravel(bar, leaving, incoming, travel, visibility)
|
|
191
194
|
}
|
|
192
195
|
|
|
196
|
+
// Whether a screen with [options] hides the bar, shown now, by a move of [change]: a slide, or the transition's
|
|
197
|
+
// side. The bar then leaves as it shows, its large title too (`applyAll`); while it goes with the screens of a
|
|
198
|
+
// transition of the stack's own ([travels]) only a side of its own moves it.
|
|
199
|
+
private fun hidesMoving(options: Options, change: StackDisplay.BarVisibility, travels: Boolean): Boolean {
|
|
200
|
+
val exit = change.side?.exit
|
|
201
|
+
return options.topBar.visible.isFalse && !options.topBar.animate.isFalse && change.animated &&
|
|
202
|
+
!AnimationRules.isTurnedOff(exit) && (!travels || AnimationRules.barSideGiven(exit))
|
|
203
|
+
}
|
|
204
|
+
|
|
193
205
|
/** The transition that brought the current child ended: the row at rest, the picture under it gone. */
|
|
194
206
|
fun endRowTransition() {
|
|
195
207
|
// A large title still opening is open now.
|
|
@@ -306,6 +318,7 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
306
318
|
* of the bar (the search) applies again.
|
|
307
319
|
*/
|
|
308
320
|
fun onInsets() {
|
|
321
|
+
if (waitsForHide) return
|
|
309
322
|
val bar = bar ?: return
|
|
310
323
|
val entry = shownEntry ?: return
|
|
311
324
|
val screen = shownScreen ?: return
|
|
@@ -380,6 +393,12 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
380
393
|
|
|
381
394
|
/** [screen] is destroyed: its components and its search go. */
|
|
382
395
|
fun screenDestroyed(screen: ViewController<*>, entryView: View?) {
|
|
396
|
+
// The bar may still show the components of the screen that goes: it is gone at once, with the options it held
|
|
397
|
+
// back, before they go; none when they are of the screen that goes.
|
|
398
|
+
if (waitsForHide) {
|
|
399
|
+
if (screen === shownScreen) waitsForHide = false
|
|
400
|
+
visibility?.hide(animated = false)
|
|
401
|
+
}
|
|
383
402
|
if (screen === shownScreen) {
|
|
384
403
|
shownScreen = null
|
|
385
404
|
shownEntry = null
|
|
@@ -466,6 +485,26 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
466
485
|
merge: Boolean,
|
|
467
486
|
delta: Options? = null,
|
|
468
487
|
) {
|
|
488
|
+
// A screen that hides the bar: the bar leaves as it shows, its look, title and buttons those of the screen
|
|
489
|
+
// before, and takes this screen's options once it is gone. Taken first, they would paint the leaving bar (the
|
|
490
|
+
// default color over a bar that was transparent).
|
|
491
|
+
val waited = waitsForHide
|
|
492
|
+
waitsForHide = false
|
|
493
|
+
if (options.topBar.visible.isFalse && (!merge || waited)) {
|
|
494
|
+
applyVisibility(options, merge)
|
|
495
|
+
waitsForHide = visibility?.isHiding == true
|
|
496
|
+
if (waitsForHide) {
|
|
497
|
+
// The list of the screen before no longer moves the bar: a move of it would stop the hide.
|
|
498
|
+
collapse?.disableCollapse(reset = false)
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Shown again by a merge before it was gone: none of this screen's options are on the bar yet. It shows as a
|
|
503
|
+
// merge shows it, by `animate`, not by the transition's side.
|
|
504
|
+
if (merge && waited) {
|
|
505
|
+
visibilityChange = StackDisplay.BarVisibility(true, null)
|
|
506
|
+
return applyAll(bar, entry, screen, options, merge = false)
|
|
507
|
+
}
|
|
469
508
|
applyLook(bar, entry, options)
|
|
470
509
|
applyTitle(bar, screen, options)
|
|
471
510
|
// The search first: its magnifier takes a place among the right buttons. It gets a copy of the group: it
|
|
@@ -794,6 +833,16 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
794
833
|
opening.cancel()
|
|
795
834
|
}
|
|
796
835
|
|
|
836
|
+
// A hide that held the options back ended: the bar, gone now, takes them.
|
|
837
|
+
private fun hidden() {
|
|
838
|
+
if (!waitsForHide) return
|
|
839
|
+
waitsForHide = false
|
|
840
|
+
val bar = bar ?: return
|
|
841
|
+
val entry = shownEntry ?: return
|
|
842
|
+
val screen = shownScreen ?: return
|
|
843
|
+
applyAll(bar, entry, screen, host.resolvedOf(entry), merge = false)
|
|
844
|
+
}
|
|
845
|
+
|
|
797
846
|
// Shown or hidden: with the transition by its side, or by `animate` in a merge.
|
|
798
847
|
private fun applyVisibility(options: Options, merge: Boolean) {
|
|
799
848
|
val visible = !options.topBar.visible.isFalse
|
|
@@ -10,9 +10,10 @@ import dev.apexnative.navigation.views.animations.AnimationRules
|
|
|
10
10
|
* stack its side (`animations.*.topBar`) plays instead — its `exit` hides, its `enter` shows; `enabled: false` shows or
|
|
11
11
|
* hides at once. A show cancels a running hide and a hide a running show, each with its own token, so `visible: false`
|
|
12
12
|
* then `true` leaves the bar shown; a motion cancelled leaves the bar at rest; a bar shown again stands in its place
|
|
13
|
-
* with its row seen (the hide on scroll's shift and fade reset).
|
|
13
|
+
* with its row seen (the hide on scroll's shift and fade reset). [onHidden] runs once a hide has made the bar `GONE`.
|
|
14
|
+
* Main thread only.
|
|
14
15
|
*/
|
|
15
|
-
internal class TopBarVisibility(private val target: Target) {
|
|
16
|
+
internal class TopBarVisibility(private val target: Target, private val onHidden: () -> Unit = {}) {
|
|
16
17
|
/** What moves: the bar's view, or the unit tests' fake. */
|
|
17
18
|
interface Target {
|
|
18
19
|
/** How far up the bar goes to hide: its height and the room over it, in pixels. */
|
|
@@ -45,6 +46,10 @@ internal class TopBarVisibility(private val target: Target) {
|
|
|
45
46
|
val isHidden: Boolean
|
|
46
47
|
get() = hidden
|
|
47
48
|
|
|
49
|
+
/** The bar is on its way out: a hide moves it and it still shows. */
|
|
50
|
+
val isHiding: Boolean
|
|
51
|
+
get() = hidden && sliding != null
|
|
52
|
+
|
|
48
53
|
/** Shows the bar: by [side] when it plays, sliding down when [animated], else at once. */
|
|
49
54
|
fun show(animated: Boolean, side: TopBarAnimationOptions? = null) {
|
|
50
55
|
val wasHidden = hidden
|
|
@@ -84,6 +89,7 @@ internal class TopBarVisibility(private val target: Target) {
|
|
|
84
89
|
if (!moves || target.isGone) {
|
|
85
90
|
target.translationY = 0f
|
|
86
91
|
target.isGone = true
|
|
92
|
+
onHidden()
|
|
87
93
|
return
|
|
88
94
|
}
|
|
89
95
|
val token = Any()
|
|
@@ -93,6 +99,7 @@ internal class TopBarVisibility(private val target: Target) {
|
|
|
93
99
|
settle()
|
|
94
100
|
target.isGone = true
|
|
95
101
|
target.translationY = 0f
|
|
102
|
+
onHidden()
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
105
|
val own = side?.takeIf { AnimationRules.sidePlays(it) }
|
|
@@ -48,15 +48,24 @@ internal class SideMenuView(context: Context) : DrawerLayout(context), SideMenuM
|
|
|
48
48
|
center.addView(content, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The part of [side], empty until [fillPart]: a drawer at that edge, the whole height of the side menu. It opens,
|
|
53
|
+
* closes and takes its width and lock without its content.
|
|
54
|
+
*/
|
|
55
|
+
fun addPart(side: Side): FrameLayout {
|
|
53
56
|
val part = FrameLayout(context)
|
|
54
|
-
part.addView(content, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
|
55
57
|
addView(part, LayoutParams(MATCH_PARENT, MATCH_PARENT, gravityOf(side)))
|
|
56
58
|
parts[side.ordinal] = part
|
|
57
59
|
return part
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/** [content] fills the part of [side], once. */
|
|
63
|
+
fun fillPart(side: Side, content: View) {
|
|
64
|
+
val part = partOf(side) ?: return
|
|
65
|
+
if (content.parent === part) return
|
|
66
|
+
part.addView(content, FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
/** The part of [side]; none when the layout has no menu there. */
|
|
61
70
|
fun partOf(side: Side): FrameLayout? = parts[side.ordinal]
|
|
62
71
|
|
|
@@ -1,15 +1,45 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.stack
|
|
2
2
|
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.drawable.BitmapDrawable
|
|
3
6
|
import android.graphics.drawable.Drawable
|
|
4
7
|
import android.view.View
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* What fills the room beside a moving screen in a transition (`StackLayout.extendEdge`), where the system's
|
|
8
11
|
* transitions between activities stretch a window's edge (`<extend>` of `activity_open_*` and `activity_close_*`): the
|
|
9
|
-
* screen's
|
|
10
|
-
* screen
|
|
12
|
+
* column of pixels at the screen's edge, stretched across the room, as the system stretches the window's. What the
|
|
13
|
+
* screen shows there goes on beside it — a gradient its content draws, not only its own background. Taken at the start
|
|
14
|
+
* of every push and pop. Main thread only.
|
|
11
15
|
*/
|
|
12
16
|
internal object ScreenEdge {
|
|
13
|
-
/**
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The column of pixels at [view]'s left edge ([left]) or right edge, made to be stretched; its background when the
|
|
19
|
+
* screen isn't laid out as it shows ([laidOut]: not after a rotation while it was out of the stack) or can't be drawn
|
|
20
|
+
* in software (a `HARDWARE` bitmap in it, a view that fails to draw), none without one either.
|
|
21
|
+
*/
|
|
22
|
+
fun pictureOf(view: View, left: Boolean, laidOut: Boolean): Drawable? =
|
|
23
|
+
(if (laidOut) columnOf(view, left) else null) ?: backgroundOf(view)
|
|
24
|
+
|
|
25
|
+
// One pixel wide and as tall as the screen: the view draws itself into a canvas that keeps that column only. Any
|
|
26
|
+
// view of the screen may fail to draw in software (a hardware bitmap, a view that wants a hardware canvas): the
|
|
27
|
+
// screen's background stands in then, as the transition must go on.
|
|
28
|
+
private fun columnOf(view: View, left: Boolean): Drawable? {
|
|
29
|
+
val width = view.width
|
|
30
|
+
val height = view.height
|
|
31
|
+
if (width <= 0 || height <= 0) return null
|
|
32
|
+
return try {
|
|
33
|
+
val column = Bitmap.createBitmap(1, height, Bitmap.Config.ARGB_8888)
|
|
34
|
+
val canvas = Canvas(column)
|
|
35
|
+
if (!left) canvas.translate(-(width - 1).toFloat(), 0f)
|
|
36
|
+
view.draw(canvas)
|
|
37
|
+
BitmapDrawable(view.resources, column)
|
|
38
|
+
} catch (e: RuntimeException) {
|
|
39
|
+
null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun backgroundOf(view: View): Drawable? =
|
|
44
|
+
view.background?.constantState?.newDrawable(view.resources)?.mutate()
|
|
15
45
|
}
|
|
@@ -145,12 +145,15 @@ class StackLayout(context: Context) : CoordinatorLayout(context) {
|
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
147
|
* [view] extends its start edge ([atStart]) or its end edge by [width] pixels while it moves, as the system's
|
|
148
|
-
* transitions between activities extend a window: its
|
|
149
|
-
* under it never shows at its edge. [dropEdge] ends it. Each frame of the move calls
|
|
148
|
+
* transitions between activities extend a window: the pixels at its edge, stretched, fill the room it moves away
|
|
149
|
+
* from, so the screen under it never shows at its edge. [dropEdge] ends it. Each frame of the move calls
|
|
150
|
+
* [edgesMoved].
|
|
150
151
|
*/
|
|
151
152
|
fun extendEdge(view: View, atStart: Boolean, width: Int) {
|
|
152
153
|
val left = atStart != (layoutDirection == LAYOUT_DIRECTION_RTL)
|
|
153
|
-
|
|
154
|
+
// A screen put back for a pop keeps the size of its last layout until the next one: after a rotation while it was
|
|
155
|
+
// out of the stack, its pixels are of another width.
|
|
156
|
+
val picture = ScreenEdge.pictureOf(view, left, laidOut = view.width == this.width) ?: return
|
|
154
157
|
edges[view] = Edge(picture, width, left)
|
|
155
158
|
invalidate()
|
|
156
159
|
}
|
|
@@ -124,6 +124,21 @@ On iOS the content moves aside, and the menu comes in under it with a parallax;
|
|
|
124
124
|
|
|
125
125
|
(iOS) 26: `left({ glass: true })` puts the menu on Liquid Glass. The glass shows where the menu's screen has no background of its own.
|
|
126
126
|
|
|
127
|
+
## When the menus are created
|
|
128
|
+
|
|
129
|
+
The screens of the menus are created with the side menu by default, with their React content. A heavy menu competes
|
|
130
|
+
with the first render of the center. `options({ menusAttachMode })` moves the creation:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
SideMenu({ left: 'Menu', center: 'Home' }).options({ menusAttachMode: 'onOpen' });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `'together'` (the default): with the center.
|
|
137
|
+
- `'afterCenter'`: right after the side menu first appears. The menus don't compete with the first render of the center and are ready at the first open.
|
|
138
|
+
- `'onOpen'`: each menu the first time it starts to open, from a swipe, `openMenu`, `visible: true` or `visible` in the layout. Until React draws the menu, it shows its background (`backgroundColor`). A second open reuses it.
|
|
139
|
+
|
|
140
|
+
The trade-off of `'onOpen'`: the first open of a menu doesn't wait for its render and shows its background for a moment, while a menu not opened yet runs no code. A push into the stack of such a menu and `mergeOptions` on its screen wait for the menu, like a tab not yet selected. The screens of the menus get appear events only when the menu opens, in every mode. The mode is read once, when the side menu is created, from its own options, else the default options; `mergeOptions` doesn't change it. With `appProvider` the screens render in the app's React tree at once in every mode, and the mode delays only their native containers. It means the same on iOS and Android.
|
|
141
|
+
|
|
127
142
|
## Where the swipe is off
|
|
128
143
|
|
|
129
144
|
A swipe from the edge can clash with the content, such as a map or a carousel. A screen turns it off **while it is on top of the center**, in its options:
|
|
@@ -148,7 +148,7 @@ From a screen of the tab, `setTabBadge(count)` needs no id: it sets the badge of
|
|
|
148
148
|
|
|
149
149
|
## When tabs are created
|
|
150
150
|
|
|
151
|
-
By default every tab is created with the tabs, so a switch shows it at once. `Tabs().lazy()` creates each tab the first time it shows. The app starts faster when its tabs are heavy, and a tab the user never opens costs nothing.
|
|
151
|
+
By default every tab is created with the tabs, so a switch shows it at once. On (iOS) a text input's `autoFocus` in a tab not selected yet does nothing then: focus the field in `useScreenAppear`. `Tabs().lazy()` creates each tab the first time it shows. The app starts faster when its tabs are heavy, and a tab the user never opens costs nothing.
|
|
152
152
|
|
|
153
153
|
```ts
|
|
154
154
|
const main = Tabs(/* … */).lazy();
|
package/docs-md/index.json
CHANGED
package/ios/CLAUDE.md
CHANGED
|
@@ -20,7 +20,7 @@ Swift is not used here: apps reach the core through the public headers (`Public/
|
|
|
20
20
|
- `Layouts/Screen/`: The screen (`ANNComponentViewController`), its presenter, readiness, its environment for JS (`ANNScreenEnvironment`), regions, safe-area math, the main list rule, previews.
|
|
21
21
|
- `Layouts/External/`: Native screens: `ANNExternalViewController`, `ANNNativeScreenCalls`.
|
|
22
22
|
- `Layouts/Stack/`: `ANNStackController`, `ANNStackQueue`, `ANNStackCommands`, `ANNStackPresenter`, `ANNPreviewPush`, the swipe-back delegate.
|
|
23
|
-
- `Layouts/Tabs/`: `ANNBottomTabsController`, its presenter, item builder, custom row,
|
|
23
|
+
- `Layouts/Tabs/`: `ANNBottomTabsController`, its presenter, item builder, custom row, More delegate, long press, dots.
|
|
24
24
|
- `Layouts/Modal/`: `ANNModalCoordinator`, `ANNModal`, `ANNPresentationGate`, `ANNModalPresentation`, `ANNModalTransitions`, sheets and popovers.
|
|
25
25
|
- `Layouts/Overlay/`: `ANNOverlayCoordinator`, `ANNOverlayWindow`.
|
|
26
26
|
- `Layouts/SideMenu/`: The side menu module and its presenter and wrapper.
|
|
@@ -106,6 +106,9 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
|
|
|
106
106
|
list would move as the push starts and UIKit would show the large title only after the transition. On iOS 26 its
|
|
107
107
|
large title also keeps a place of the core's for the push (`arrivesByPushOn:`). Check Showcase 1.4 (with a font
|
|
108
108
|
size), every section panel and 12.6 on push, pop and swipe after any change to waits, parking or the large title.
|
|
109
|
+
A tab rendered before it shows keeps its parent, yet UIKit gives its views no safe area either: it renders parked
|
|
110
|
+
in the tabs' view with places measured in the shown tab's view, then leaves the parking, since the core takes a view
|
|
111
|
+
in a window for one on screen (`isInUnseenTab:`); its screens keep that place until they appear.
|
|
109
112
|
- Every change of a stack's children goes through its `ANNStackQueue`. The core's own calls to UIKit's navigation
|
|
110
113
|
come only from there; UIKit's own pops are followed (`uikitShows:`), not fought.
|
|
111
114
|
- Every UIKit present and dismiss in the main window is a step in `ANNPresentationGate`. UIKit drops a transition
|
|
@@ -133,7 +136,10 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
|
|
|
133
136
|
- Use an `@available` check for newer OS APIs, and `#if __IPHONE_OS_VERSION_MAX_ALLOWED >= …` for names of a newer SDK.
|
|
134
137
|
- React Native hears of the app's light or dark style only from its hosting views in the window. A view that stands in
|
|
135
138
|
for one (`ANNSlotScreenView`, the screens of one tree) posts the trait change itself, with the window filter of
|
|
136
|
-
`ANNSurfaceHostingView`; without it `useColorScheme` keeps the old theme.
|
|
139
|
+
`ANNSurfaceHostingView`; without it `useColorScheme` keeps the old theme. `Appearance.setColorScheme` sets the style
|
|
140
|
+
only on the windows there are at that moment: every window the core makes but the main window comes from
|
|
141
|
+
`+[ANNUtils makeWindowOfClass:]`, which gives it the main window's style. A window of the system's style would draw
|
|
142
|
+
its screens in the other theme, and its hosting views would switch the app's theme in JS.
|
|
137
143
|
- A step that runs later in a command (a ready handler, a timer, a transition's end) goes through the owner's guard
|
|
138
144
|
(`protect:step:`), so an exception rejects the command instead of crashing the app. So does the start of a wait
|
|
139
145
|
(the root, a modal, an overlay: their record is listed and parked by then), and the modal's end steps inside UIKit's
|
package/ios/CORE.md
CHANGED
|
@@ -128,8 +128,8 @@ Each of these owns one rule. Call it; do not copy it.
|
|
|
128
128
|
- `ANNNavigationState`: The visible screen and the nearest tabs it is in (the last tabs met walking down); coalesced updates, holds, the state now. Called by: The dispatcher, `ANNCommandAnswer`, `ANNRootCoordinator`, `ANNModalCoordinator`, `ANNBottomTabsController`.
|
|
129
129
|
- `ANNScreenReadiness`: The signals of a screen's safe area view (has one, laid out, content rendered), sticky, by screen id. Called by: `ANNSafeAreaViewComponentView`, `ANNReactView`.
|
|
130
130
|
- `ANNRenderWait`: The one wait for the render of what a command shows: content, bar components, limits, the unknown-content rule. Called by: Root, stack queue, modals, overlays, `showInColumn`.
|
|
131
|
-
- `ANNParking`: Where a view waits for its content unseen: the window, a stack's view, a preview's host view. Called by: Root, stack, modals, overlays, `ANNPreviewPush
|
|
132
|
-
- `ANNScreenPlace`: The safe area a parked screen lays out with, measured where it will show on top of a stack. Called by: `ANNStackController` (`stackPark:`), `ANNSplitViewCommands` (`showInColumn`), through `placeWhileParked` of the screen.
|
|
131
|
+
- `ANNParking`: Where a view waits for its content unseen: the window, a stack's view, a preview's host view, the tabs' view. Called by: Root, stack, modals, overlays, `ANNPreviewPush`, tabs (a tab rendered before it shows).
|
|
132
|
+
- `ANNScreenPlace`: The safe area a parked screen lays out with, measured where it will show on top of a stack. Called by: `ANNStackController` (`stackPark:`), `ANNSplitViewCommands` (`showInColumn`), the tabs (a tab rendered before it shows, measured in the shown tab's view), through `placeWhileParked` of the screen.
|
|
133
133
|
- `ANNPendingLayers`: Layers that wait to show (a pending root, a modal, an overlay); a node inside counts as off screen. Called by: The layer's owner; nodes ask.
|
|
134
134
|
- `ANNShownScreens`: "What a node shows changed": tells the pending layer, the modal hold and the system UI, in that order. Called by: Every container that changes what it shows.
|
|
135
135
|
- `ANNModalCoordinator`, `ANNModal`: One record per modal, its states and waiting commands. Called by: Commands, root, state, repaint, reload.
|
|
@@ -11,10 +11,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
11
11
|
typedef NS_ENUM(NSInteger, ANNWaitedReadiness) {
|
|
12
12
|
/// The screen's content rendered: what the user sees.
|
|
13
13
|
ANNWaitedReadinessContent,
|
|
14
|
-
/// The screen's
|
|
15
|
-
/// renders
|
|
16
|
-
///
|
|
17
|
-
|
|
14
|
+
/// The screen's content rendered, on a screen the user doesn't see yet (an unselected tab of `together`, which
|
|
15
|
+
/// renders unseen in the tabs' view). Its part of a wait ends at the command's 0.5 s at the latest, and its bar
|
|
16
|
+
/// components are not waited for.
|
|
17
|
+
ANNWaitedReadinessUnseen,
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/// A transition of the navigation is done: what it moved is in place.
|
|
@@ -97,7 +97,7 @@ typedef void (^ANNTransitionRejectionBlock)(NSString *message, NSError *_Nullabl
|
|
|
97
97
|
- (NSArray<UIViewController *> *)waitedComponentViewControllers;
|
|
98
98
|
|
|
99
99
|
/// The readiness a wait needs of `screen`, a waited screen inside this node; a node without an answer leaves it to its
|
|
100
|
-
/// parents (`ANNWaitedReadinessOf`). The tabs answer "
|
|
100
|
+
/// parents (`ANNWaitedReadinessOf`). The tabs answer "unseen" for the screens of their unselected tabs.
|
|
101
101
|
- (ANNWaitedReadiness)waitedReadinessOfScreen:(UIViewController *)screen;
|
|
102
102
|
|
|
103
103
|
#pragma mark - The children
|
package/ios/Core/ANNParking.h
CHANGED
|
@@ -4,10 +4,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
4
4
|
|
|
5
5
|
/// A place where the views of nodes wait for their content unseen (their safe area views lay out only in a window): the
|
|
6
6
|
/// window under the current root while a root waits, a stack's view under UIKit's container while a screen of the stack
|
|
7
|
-
/// waits or renders
|
|
8
|
-
///
|
|
9
|
-
///
|
|
10
|
-
///
|
|
7
|
+
/// waits or renders, the tabs' view while a tab rendered before it shows renders its content. A parked view sits at the
|
|
8
|
+
/// back of its host, at the host's size and following it (a rotation, a resize), gets no touches and is hidden from
|
|
9
|
+
/// accessibility; it isn't a child of anything there (a tab stays the tabs' child), and its node's appearance is driven
|
|
10
|
+
/// meanwhile (`ANNAppearanceDispatcher driveAppearanceOf:`), so UIKit's appearance callbacks report nothing for it.
|
|
11
|
+
/// Each node leaves it as it was. Main queue.
|
|
11
12
|
@interface ANNParking : NSObject
|
|
12
13
|
|
|
13
14
|
/// `node`'s view waits in `host` from now on, with what it had kept. NO when it waits here already.
|