@apexnative/navigation 1.0.0-alpha.3 → 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.
- package/CHANGELOG.md +9 -0
- package/android/CLAUDE.md +17 -7
- package/android/CORE.md +8 -3
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +16 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +6 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +58 -15
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +10 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/cli/doctor/checks/app.js +61 -13
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/docs-md/architecture/transitions.md +6 -7
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/help/troubleshooting.md +20 -3
- package/docs-md/index.json +4 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +2 -2
- package/docs-md/start/installation.md +3 -1
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +13 -8
- package/ios/Contract/ANNCommandAnswer.mm +5 -20
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
- package/ios/Core/ANNErrorHandler.h +1 -1
- package/ios/Core/ANNErrorHandler.mm +3 -1
- package/ios/Core/ANNEventEmitter.h +1 -1
- package/ios/Core/ANNEventEmitter.mm +2 -2
- package/ios/Core/ANNJavaScriptCalls.h +34 -0
- package/ios/Core/ANNJavaScriptCalls.mm +61 -0
- package/ios/Core/ANNNavigationState.h +7 -3
- package/ios/Core/ANNNavigationState.mm +10 -5
- package/ios/Core/ANNNodeHelper.h +32 -12
- package/ios/Core/ANNNodeHelper.mm +100 -3
- package/ios/Core/ANNRenderWait.h +15 -1
- package/ios/Core/ANNRenderWait.mm +119 -17
- package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
- package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
- package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
- package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
- package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
- package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
- package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
- package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
- package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
- package/ios/Layouts/Stack/ANNStackController.mm +26 -3
- package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
- package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
- package/ios/Options/ANNOptionsCache.h +7 -6
- package/ios/Options/ANNSearchBarOptions.h +4 -3
- package/ios/Options/ANNUserChoice.h +57 -0
- package/ios/Options/ANNUserChoice.mm +44 -0
- package/ios/Options/Params/ANNImageSource.h +61 -0
- package/ios/Options/Params/ANNImageSource.mm +101 -0
- package/ios/Options/Params/ANNOptionImages.h +37 -0
- package/ios/Options/Params/ANNOptionImages.mm +284 -0
- package/ios/Options/Params/Image.h +11 -2
- package/ios/Options/Params/Image.mm +15 -2
- package/ios/Options/Params/ImageParser.h +5 -5
- package/ios/Options/Params/ImageParser.mm +28 -46
- package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
- package/ios/RECIPES.md +11 -5
- package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
- package/ios/React/Slots/ANNSlotContainers.h +14 -6
- package/ios/React/Slots/ANNSlotContainers.mm +75 -2
- package/ios/TopBar/ANNBarButtons.h +9 -5
- package/ios/TopBar/ANNBarButtons.mm +211 -63
- package/ios/TopBar/ANNIconButtonImage.h +1 -0
- package/ios/TopBar/ANNIconButtonImage.mm +34 -1
- package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
- package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
- package/ios/TopBar/ANNSearchPresenter.mm +170 -19
- package/ios/TopBar/ANNTopBarPresenter.h +5 -0
- package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
- package/ios/TurboModules/ANNTurboModule.mm +39 -21
- package/lib/module/ARCHITECTURE.md +18 -18
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/navigation.js.map +1 -1
- package/lib/module/engine/commands.js +9 -7
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +13 -9
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/options.js +44 -11
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/places.js +44 -7
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/props.js +30 -1
- package/lib/module/engine/props.js.map +1 -1
- package/lib/module/engine/rememberedOptions.js +1 -1
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +22 -12
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/module/interfaces/contract/commands.json +101 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/navigation.d.ts +4 -1
- package/lib/typescript/api/navigation.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/options.d.ts +5 -2
- package/lib/typescript/engine/options.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +5 -2
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/props.d.ts +8 -1
- package/lib/typescript/engine/props.d.ts.map +1 -1
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
- package/lib/typescript/interfaces/Events.d.ts +19 -10
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +5 -2
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
- package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -18
- package/src/adapters/NativeANNTurboModule.ts +5 -2
- package/src/api/events/navigationEvents.ts +1 -1
- package/src/api/hooks/useNavigation.ts +1 -1
- package/src/api/navigation.ts +4 -1
- package/src/engine/commands.ts +19 -7
- package/src/engine/layout.ts +16 -9
- package/src/engine/options.ts +39 -11
- package/src/engine/places.ts +56 -7
- package/src/engine/props.ts +30 -1
- package/src/engine/rememberedOptions.ts +4 -1
- package/src/engine/screens.tsx +23 -13
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/Events.ts +19 -10
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +5 -2
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/topBar.ts +22 -9
|
@@ -288,8 +288,8 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
|
|
|
288
288
|
screen.whenContentMounted(
|
|
289
289
|
waiting = waiting,
|
|
290
290
|
mounted = {
|
|
291
|
-
// The frame of the mount: the screen shows transparent and lays out
|
|
292
|
-
//
|
|
291
|
+
// The frame of the mount: the screen shows transparent and lays out. The pre-draw of that frame hands
|
|
292
|
+
// the start to the queue, whose transition begins behind the calls JavaScript made before.
|
|
293
293
|
screen.peekView()?.let {
|
|
294
294
|
it.alpha = 0f
|
|
295
295
|
it.visibility = View.VISIBLE
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt
CHANGED
|
@@ -172,19 +172,20 @@ internal class FabPresenter(
|
|
|
172
172
|
alignStart = options.alignHorizontally.get("right") == "left"
|
|
173
173
|
val source = source
|
|
174
174
|
fab?.let { fab ->
|
|
175
|
-
applyButton(fab, options)
|
|
175
|
+
applyButton(fab, options, mayWait = fab.drawable != null || options.visible.isFalse)
|
|
176
176
|
TestIds.apply(fab, options.testID.takeIf { it.hasValue() }?.get())
|
|
177
177
|
val id = shownId.orEmpty()
|
|
178
178
|
fab.setOnClickListener { press(source, id) }
|
|
179
179
|
}
|
|
180
180
|
menu?.let { menu ->
|
|
181
|
-
applyButton(menu.main, options)
|
|
181
|
+
applyButton(menu.main, options, mayWait = menu.main.drawable != null || options.visible.isFalse)
|
|
182
182
|
TestIds.apply(menu.main, options.testID.takeIf { it.hasValue() }?.get())
|
|
183
183
|
menu.scrimColor = options.scrimColor.get(activity, null)
|
|
184
184
|
menu.setActions(options.actions.orEmpty().map { action ->
|
|
185
185
|
val actionId = action.id.get("")
|
|
186
186
|
Fab(activity, actionId).also { button ->
|
|
187
|
-
|
|
187
|
+
// The actions show only while the menu is open.
|
|
188
|
+
applyButton(button, action, mayWait = !menu.isOpen)
|
|
188
189
|
TestIds.apply(button, action.testID.takeIf { it.hasValue() }?.get())
|
|
189
190
|
button.setOnClickListener {
|
|
190
191
|
press(source, actionId)
|
|
@@ -205,9 +206,10 @@ internal class FabPresenter(
|
|
|
205
206
|
place()
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
|
|
209
|
+
// [mayWait]: the button can take its icon when it is ready (it shows one, or it stays hidden), see [Fab.applyIcon].
|
|
210
|
+
private fun applyButton(button: Fab, options: FabOptions, mayWait: Boolean) {
|
|
209
211
|
val icon = options.icon.takeIf { it.hasValue() }?.get()?.ifEmpty { null }
|
|
210
|
-
if (icon != null) button.applyIcon(icon, options.iconColor)
|
|
212
|
+
if (icon != null) button.applyIcon(icon, options.iconColor, mayWait)
|
|
211
213
|
applyColors(button, options)
|
|
212
214
|
button.setMini(options.size.get("regular") == "mini")
|
|
213
215
|
}
|
|
@@ -14,7 +14,6 @@ import androidx.core.view.WindowCompat
|
|
|
14
14
|
import androidx.core.view.WindowInsetsCompat
|
|
15
15
|
import com.facebook.react.ReactApplication
|
|
16
16
|
import dev.apexnative.navigation.options.SearchBarOptions
|
|
17
|
-
import dev.apexnative.navigation.options.params.Text
|
|
18
17
|
import dev.apexnative.navigation.options.params.ThemeColour
|
|
19
18
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
20
19
|
import dev.apexnative.navigation.utils.dp
|
|
@@ -148,10 +147,11 @@ class StackSearchPresenter(
|
|
|
148
147
|
|
|
149
148
|
private fun searchOf(screen: ViewController<*>): ScreenSearch =
|
|
150
149
|
searches.getOrPut(screen.view) {
|
|
151
|
-
// The search of a pair of screens is its primary's: its events name that screen, which keeps the scope
|
|
150
|
+
// The search of a pair of screens is its primary's: its events name that screen, which keeps the scope in
|
|
151
|
+
// its user layer, where a merge that sends the same options again doesn't take it away.
|
|
152
152
|
val owner = (screen as? OptionsScreen)?.optionsScreen ?: screen
|
|
153
153
|
ScreenSearch(this, owner.id, styleContext(), loadIcon) { scope ->
|
|
154
|
-
owner.
|
|
154
|
+
owner.changeChoices { it.pickScope(scope) }
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
package dev.apexnative.navigation.viewcontrollers.stack.topbar
|
|
2
2
|
|
|
3
|
-
import dev.apexnative.navigation.options.ButtonMenuOptions
|
|
4
|
-
import dev.apexnative.navigation.options.ButtonOptions
|
|
5
3
|
import dev.apexnative.navigation.options.Options
|
|
6
|
-
import dev.apexnative.navigation.options.
|
|
4
|
+
import dev.apexnative.navigation.options.UserChoices
|
|
7
5
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
|
-
* The check of a single-selection menu the user picked: written
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* The check of a single-selection menu the user picked: written into the screen's user layer
|
|
9
|
+
* ([ViewController.changeChoices]), never into its own options nor into a menu other screens share (the stack's, the
|
|
10
|
+
* defaults'). It goes over the menu as the screen's options resolve, by the button's id and the item's id, and stays
|
|
11
|
+
* until the app changes that menu's checks or takes the item away. Main thread only.
|
|
14
12
|
*/
|
|
15
13
|
internal object MenuChecks {
|
|
16
14
|
/**
|
|
@@ -19,54 +17,13 @@ internal object MenuChecks {
|
|
|
19
17
|
*/
|
|
20
18
|
fun write(screen: ViewController<*>, resolved: Options, buttonId: String?, itemId: String) {
|
|
21
19
|
val topBar = resolved.topBar
|
|
22
|
-
if (buttonId == null) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
val left = topBar.buttons.left?.any { it.id.get(null) == buttonId } == true
|
|
33
|
-
val list = (if (left) topBar.buttons.left else topBar.buttons.right) ?: return
|
|
34
|
-
val menu = list.firstOrNull { it.id.get(null) == buttonId }?.menu ?: return
|
|
35
|
-
if (!checks(menu, itemId)) return
|
|
36
|
-
screen.changeOptions { own ->
|
|
37
|
-
val ownList = (if (left) own.topBar.buttons.left else own.topBar.buttons.right)
|
|
38
|
-
?: list.map(ButtonOptions::copyWithMenu)
|
|
39
|
-
val button = ownList.firstOrNull { it.id.get(null) == buttonId } ?: return@changeOptions
|
|
40
|
-
val copy = (button.menu ?: menu).copyMenu()
|
|
41
|
-
check(copy, itemId)
|
|
42
|
-
button.menu = copy
|
|
43
|
-
if (left) own.topBar.buttons.left = ownList else own.topBar.buttons.right = ownList
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Whether picking [itemId] moves a check: its level has `singleSelection`. */
|
|
48
|
-
fun checks(menu: ButtonMenuOptions, itemId: String): Boolean = levelOf(menu.items, menu.singleSelection, itemId) == true
|
|
49
|
-
|
|
50
|
-
/** Moves the check of [itemId]'s level to it, in [menu] itself. */
|
|
51
|
-
fun check(menu: ButtonMenuOptions, itemId: String) {
|
|
52
|
-
mark(menu.items, menu.singleSelection, itemId)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Whether the level holding [itemId] has single selection; null when [itemId] isn't in [elements].
|
|
56
|
-
private fun levelOf(elements: List<ButtonMenuOptions.Element>, single: Boolean, itemId: String): Boolean? {
|
|
57
|
-
if (elements.any { it is ButtonMenuOptions.Item && it.id == itemId }) return single
|
|
58
|
-
for (element in elements) {
|
|
59
|
-
if (element !is ButtonMenuOptions.Submenu) continue
|
|
60
|
-
levelOf(element.items, element.singleSelection, itemId)?.let { return it }
|
|
61
|
-
}
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private fun mark(elements: List<ButtonMenuOptions.Element>, single: Boolean, itemId: String): Boolean {
|
|
66
|
-
if (elements.any { it is ButtonMenuOptions.Item && it.id == itemId }) {
|
|
67
|
-
if (single) elements.forEach { if (it is ButtonMenuOptions.Item) it.checked = it.id == itemId }
|
|
68
|
-
return true
|
|
69
|
-
}
|
|
70
|
-
return elements.any { it is ButtonMenuOptions.Submenu && mark(it.items, it.singleSelection, itemId) }
|
|
20
|
+
val menu = if (buttonId == null) {
|
|
21
|
+
topBar.overflowMenu
|
|
22
|
+
} else {
|
|
23
|
+
(topBar.buttons.left.orEmpty() + topBar.buttons.right.orEmpty()).firstOrNull { it.id.get(null) == buttonId }
|
|
24
|
+
?.menu
|
|
25
|
+
} ?: return
|
|
26
|
+
if (!UserChoices.checks(menu, itemId)) return
|
|
27
|
+
screen.changeChoices { it.check(buttonId, menu, itemId) }
|
|
71
28
|
}
|
|
72
29
|
}
|
|
@@ -1008,8 +1008,16 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
1008
1008
|
button.priority.get(null),
|
|
1009
1009
|
).joinToString("|")
|
|
1010
1010
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1011
|
+
// What the overflow menu shows, its submenus and checks with it: a check the app moves, or one of the user layer
|
|
1012
|
+
// that leaves, fills the menu again.
|
|
1013
|
+
private fun menuKey(menu: ButtonMenuOptions): String = elementsKey(menu.items) + menu.testID
|
|
1014
|
+
|
|
1015
|
+
private fun elementsKey(elements: List<ButtonMenuOptions.Element>): String = elements.joinToString("|") {
|
|
1016
|
+
"${it.title}:${it.icon}:${it.testID}" + when (it) {
|
|
1017
|
+
is ButtonMenuOptions.Item -> ":${it.checked}:${it.disabled}"
|
|
1018
|
+
is ButtonMenuOptions.Submenu -> "[${elementsKey(it.items)}]"
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1013
1021
|
|
|
1014
1022
|
private fun fontKey(font: FontOptions): String =
|
|
1015
1023
|
listOf(font.fontFamily, font.fontStyle, font.fontWeight).joinToString { it.get(null).toString() }
|
|
@@ -6,6 +6,7 @@ import android.view.ViewGroup
|
|
|
6
6
|
import dev.apexnative.navigation.options.DefaultOptions
|
|
7
7
|
import dev.apexnative.navigation.options.Options
|
|
8
8
|
import dev.apexnative.navigation.options.OptionsCache
|
|
9
|
+
import dev.apexnative.navigation.options.UserChoices
|
|
9
10
|
import dev.apexnative.navigation.options.params.Bool
|
|
10
11
|
import dev.apexnative.navigation.options.params.NullBool
|
|
11
12
|
import dev.apexnative.navigation.react.CommandListener
|
|
@@ -30,11 +31,11 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
30
31
|
var initialOptions: Options,
|
|
31
32
|
) {
|
|
32
33
|
/**
|
|
33
|
-
* The node's own options: those it was made with, with every `mergeOptions`
|
|
34
|
-
* Writing them is one of the
|
|
35
|
-
* options, keeps the object (a stripped copy when it has a one-shot option: the
|
|
36
|
-
* makes every resolved result stale. Don't change the object in place:
|
|
37
|
-
* changed copy.
|
|
34
|
+
* The node's own options: those it was made with, with every `mergeOptions` over them; what the user chose in the
|
|
35
|
+
* native UI is not here but in [choices]. Writing them is one of the places the options change ([OptionsCache]):
|
|
36
|
+
* the setter takes away the one-shot options, keeps the object (a stripped copy when it has a one-shot option: the
|
|
37
|
+
* caller's object never changes), and makes every resolved result stale. Don't change the object in place:
|
|
38
|
+
* [changeOptions] or [mergeOptions] write a changed copy.
|
|
38
39
|
*/
|
|
39
40
|
var options: Options = initialOptions.copy().also(Options::clearOneShotOptions)
|
|
40
41
|
set(value) {
|
|
@@ -42,6 +43,10 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
42
43
|
OptionsCache.optionsChanged()
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// The user layer: what the user chose in the native UI (a menu's check, a search scope), over the node's own
|
|
47
|
+
// options as they resolve ([changeChoices]).
|
|
48
|
+
private val choices = UserChoices()
|
|
49
|
+
|
|
45
50
|
/**
|
|
46
51
|
* The JavaScript the node was made for (`ReloadCoordinator`): the events of the node and its screens reach only
|
|
47
52
|
* that one.
|
|
@@ -156,9 +161,9 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
/**
|
|
159
|
-
* The node's options over those of the layouts around it, without the default options
|
|
160
|
-
* then the node's own. A new object at each call, which the caller may
|
|
161
|
-
* what it shows with: that is [resolveShownOptions].
|
|
164
|
+
* The node's options over those of the layouts around it, without the default options and without the user layer
|
|
165
|
+
* ([changeChoices]): the parents' resolution, then the node's own. A new object at each call, which the caller may
|
|
166
|
+
* change. For a layout of nodes this is not what it shows with: that is [resolveShownOptions].
|
|
162
167
|
*/
|
|
163
168
|
open fun resolveCurrentOptions(): Options {
|
|
164
169
|
val resolved = parent?.resolveChildOptions(this) ?: Options()
|
|
@@ -166,21 +171,21 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
/**
|
|
169
|
-
* The node's options over those of the layouts around it and [defaultOptions]
|
|
170
|
-
* default options ([DefaultOptions.current]) the result is
|
|
171
|
-
* ([OptionsCache]) and the layouts around the node and its current
|
|
172
|
-
* changes it copies it first. Main thread only.
|
|
174
|
+
* The node's options over those of the layouts around it and [defaultOptions], with the user layer over them
|
|
175
|
+
* ([changeChoices]): what it shows with. With the app's default options ([DefaultOptions.current]) the result is
|
|
176
|
+
* kept and given again while no options changed ([OptionsCache]) and the layouts around the node and its current
|
|
177
|
+
* children are the same: it is read-only, whoever changes it copies it first. Main thread only.
|
|
173
178
|
*
|
|
174
179
|
* For a layout of nodes (a stack, tabs, a side menu) it is the layout's own options over those around it, without
|
|
175
180
|
* any child's: not what the layout shows with, which [resolveShownOptions] gives.
|
|
176
181
|
*/
|
|
177
182
|
open fun resolveCurrentOptions(defaultOptions: Options): Options {
|
|
178
|
-
if (defaultOptions !== DefaultOptions.current) return
|
|
183
|
+
if (defaultOptions !== DefaultOptions.current) return withChoices(defaultOptions)
|
|
179
184
|
val generation = OptionsCache.generation
|
|
180
185
|
val along = resolvedAlong()
|
|
181
186
|
val kept = resolved
|
|
182
187
|
if (kept != null && resolvedGeneration == generation && sameNodes(along, resolvedAlong)) return kept
|
|
183
|
-
val result =
|
|
188
|
+
val result = withChoices(defaultOptions)
|
|
184
189
|
resolved = result
|
|
185
190
|
resolvedGeneration = generation
|
|
186
191
|
resolvedAlong = along.map(::WeakReference)
|
|
@@ -196,6 +201,10 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
196
201
|
*/
|
|
197
202
|
open fun resolveShownOptions(defaultOptions: Options): Options = resolveCurrentOptions(defaultOptions)
|
|
198
203
|
|
|
204
|
+
// The resolution in its order: the defaults, the parents, the node's own options, then the user layer over them.
|
|
205
|
+
private fun withChoices(defaultOptions: Options): Options =
|
|
206
|
+
choices.applyTo(resolveCurrentOptions().withDefaultOptions(defaultOptions))
|
|
207
|
+
|
|
199
208
|
// The layouts around this node, nearest first, then its current children down the visible chain.
|
|
200
209
|
private fun resolvedAlong(): List<ViewController<*>> {
|
|
201
210
|
val nodes = ArrayList<ViewController<*>>()
|
|
@@ -216,9 +225,9 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
216
225
|
nodes.size == kept.size && nodes.indices.all { nodes[it] === kept[it].get() }
|
|
217
226
|
|
|
218
227
|
/**
|
|
219
|
-
* Changes the node's own options: [change] gets a copy, which is stored ([options]).
|
|
220
|
-
*
|
|
221
|
-
*
|
|
228
|
+
* Changes the node's own options: [change] gets a copy, which is stored ([options]). It is for the library's own
|
|
229
|
+
* bookkeeping of a merge (a side color written into the node's buttons, the back button's visibility); what the
|
|
230
|
+
* user chose goes through [changeChoices].
|
|
222
231
|
*/
|
|
223
232
|
fun changeOptions(change: (Options) -> Unit) {
|
|
224
233
|
val copy = options.copy()
|
|
@@ -226,14 +235,40 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
226
235
|
options = copy
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Changes the node's user layer: what the user chose in the native UI (a single-selection menu's check, a search
|
|
240
|
+
* scope). It goes over the node's own options as they resolve, by the ids of the buttons, items and scopes, and
|
|
241
|
+
* stays through an unrelated `mergeOptions`, the same values sent again and `setDefaultOptions`; a merge that
|
|
242
|
+
* changes the chosen field takes it away ([mergeOwnOptions]). Every resolved result is stale after it
|
|
243
|
+
* ([OptionsCache]).
|
|
244
|
+
*/
|
|
245
|
+
internal fun changeChoices(change: (UserChoices) -> Unit) {
|
|
246
|
+
change(choices)
|
|
247
|
+
OptionsCache.optionsChanged()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* [delta] of a `mergeOptions` goes over the node's own options ([options]); a choice of the user layer whose field
|
|
252
|
+
* the merge changes, or whose button, item or scope it took away, leaves, so the app's value shows
|
|
253
|
+
* (`UserChoices.yieldTo`). Every `mergeOptions` into a node writes its own options this way.
|
|
254
|
+
*/
|
|
255
|
+
protected fun mergeOwnOptions(delta: Options) {
|
|
256
|
+
val own = options
|
|
257
|
+
val before = if (choices.isEmpty) null else resolveCurrentOptions().withDefaultOptions(DefaultOptions.current)
|
|
258
|
+
options = own.copy().mergeWith(delta)
|
|
259
|
+
before ?: return
|
|
260
|
+
val after = resolveCurrentOptions().withDefaultOptions(DefaultOptions.current)
|
|
261
|
+
if (choices.yieldTo(delta, own, before, after)) OptionsCache.optionsChanged()
|
|
262
|
+
}
|
|
263
|
+
|
|
229
264
|
/**
|
|
230
265
|
* `mergeOptions` with the node's id (`mergeOptions`): [options] go over its own and stay there (one-shot options
|
|
231
|
-
* not kept); what shows applies them ([applyMergedOptions], when the view shows; otherwise as it
|
|
232
|
-
* layouts around it apply their part (`ParentController.mergeChildOptions`, each passes it on).
|
|
233
|
-
* carry the one-shot options to them. Containers that hand the merge to a child override it.
|
|
266
|
+
* not kept, [mergeOwnOptions]); what shows applies them ([applyMergedOptions], when the view shows; otherwise as it
|
|
267
|
+
* appears); the layouts around it apply their part (`ParentController.mergeChildOptions`, each passes it on).
|
|
268
|
+
* [options] still carry the one-shot options to them. Containers that hand the merge to a child override it.
|
|
234
269
|
*/
|
|
235
270
|
open fun mergeOptions(options: Options) {
|
|
236
|
-
|
|
271
|
+
mergeOwnOptions(options)
|
|
237
272
|
if (isViewShown) applyMergedOptions(options)
|
|
238
273
|
performOnParentController { it.mergeChildOptions(options, this) }
|
|
239
274
|
}
|
|
@@ -228,13 +228,15 @@ class BottomTabs(context: Context) : BottomNavigationView(MaterialTheme.context(
|
|
|
228
228
|
applyLongPressesAgain()
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
/** Null: the item has no icon. */
|
|
232
|
+
fun setIcon(index: Int, icon: Drawable?) {
|
|
232
233
|
val item = items.getOrNull(index) ?: return
|
|
233
234
|
if (item.icon == icon) return
|
|
234
235
|
updateIcon(index, item.copy(icon = icon))
|
|
235
236
|
}
|
|
236
237
|
|
|
237
|
-
|
|
238
|
+
/** Null: the item shows its icon when selected too. */
|
|
239
|
+
fun setSelectedIcon(index: Int, icon: Drawable?) {
|
|
238
240
|
val item = items.getOrNull(index) ?: return
|
|
239
241
|
if (item.selectedIcon == icon) return
|
|
240
242
|
updateIcon(index, item.copy(selectedIcon = icon))
|
|
@@ -4,12 +4,11 @@ import android.content.Context
|
|
|
4
4
|
import android.content.res.ColorStateList
|
|
5
5
|
import android.content.res.Configuration
|
|
6
6
|
import android.graphics.Color
|
|
7
|
-
import android.graphics.drawable.Drawable
|
|
8
7
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
9
8
|
import dev.apexnative.navigation.options.params.ThemeColour
|
|
10
|
-
import dev.apexnative.navigation.utils.
|
|
11
|
-
import dev.apexnative.navigation.utils.ImageLoadingListenerAdapter
|
|
9
|
+
import dev.apexnative.navigation.utils.ImageRequests
|
|
12
10
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
11
|
+
import dev.apexnative.navigation.utils.OptionImages
|
|
13
12
|
import dev.apexnative.navigation.utils.UiUtils
|
|
14
13
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
|
|
15
14
|
import androidx.appcompat.R as AppCompatR
|
|
@@ -29,6 +28,7 @@ class Fab(context: Context, val fabId: String) : FloatingActionButton(MaterialTh
|
|
|
29
28
|
// The icon's color of the options: a container color merged later kept the theme's icon color instead.
|
|
30
29
|
private var iconColor: Int? = null
|
|
31
30
|
private var optionRippleColor: Int? = null
|
|
31
|
+
private val icons = ImageRequests<Unit>(OptionImages.source(this.context))
|
|
32
32
|
|
|
33
33
|
init {
|
|
34
34
|
// Material's theme overlay of the button is a copy of the theme, made once: it follows a light/dark switch too.
|
|
@@ -36,14 +36,18 @@ class Fab(context: Context, val fabId: String) : FloatingActionButton(MaterialTh
|
|
|
36
36
|
MaterialTheme.followOverlay(this.context)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The icon of the options, with its color. [mayWait]: the button can show it when it is ready (it shows an icon
|
|
41
|
+
* already, it is hidden with `visible: false`, or it is an action of a closed menu), so an icon the cache doesn't
|
|
42
|
+
* have loads off the main thread; the last icon asked for wins. Otherwise it loads at once. An icon that doesn't
|
|
43
|
+
* load leaves the button without an icon.
|
|
44
|
+
*/
|
|
45
|
+
fun applyIcon(icon: String, color: ThemeColour, mayWait: Boolean) {
|
|
40
46
|
if (color.hasValue()) iconColor = color.get()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
+
icons.request(Unit, icon, mayWait) { drawable ->
|
|
48
|
+
setImageDrawable(drawable)
|
|
49
|
+
iconColor?.let { imageTintList = ColorStateList.valueOf(it) }
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/** iconColor alone, without a new icon: it did nothing in a merge. */
|
package/cli/doctor/checks/app.js
CHANGED
|
@@ -11,8 +11,10 @@ const {
|
|
|
11
11
|
reactNativeVersion,
|
|
12
12
|
usesExpo,
|
|
13
13
|
} = require('../../app');
|
|
14
|
+
const { mainActivity } = require('../../links/native');
|
|
14
15
|
const { findProject } = require('../../project');
|
|
15
16
|
const { codeOf } = require('../../setup/source');
|
|
17
|
+
const { loadsReactNativeItself } = require('../../setup/steps/mainApplication');
|
|
16
18
|
const { planCodegen } = require('../../codegen');
|
|
17
19
|
const { readConfig } = require('../../config');
|
|
18
20
|
const { planProject } = require('../../setup');
|
|
@@ -27,6 +29,16 @@ const { splashChecks } = require('./splash');
|
|
|
27
29
|
const SETUP = { label: 'npx apex-navigation setup', run: /** @type {const} */ ('setup') };
|
|
28
30
|
const GENERATED = ['ApexNavigation.generated.swift', 'ApexNavigationGenerated.kt'];
|
|
29
31
|
const CODEGEN = { label: 'npx apex-navigation codegen', run: /** @type {const} */ ('codegen') };
|
|
32
|
+
// The changes the main activity must handle itself; the template's value has them all.
|
|
33
|
+
const CONFIG_CHANGES = [
|
|
34
|
+
'orientation',
|
|
35
|
+
'screenSize',
|
|
36
|
+
'smallestScreenSize',
|
|
37
|
+
'screenLayout',
|
|
38
|
+
'uiMode',
|
|
39
|
+
];
|
|
40
|
+
const TEMPLATE_CONFIG_CHANGES =
|
|
41
|
+
'keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode';
|
|
30
42
|
|
|
31
43
|
/**
|
|
32
44
|
* React Native, the navigation, and the files setup sets up.
|
|
@@ -122,28 +134,64 @@ function appChecks({ root, run }) {
|
|
|
122
134
|
});
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
const configChanges = configChangesCheck(project.manifest);
|
|
138
|
+
if (configChanges) checks.push(configChanges);
|
|
139
|
+
|
|
125
140
|
checks.push(...splashChecks(root, project), ...iconChecks(root, project));
|
|
126
141
|
|
|
127
|
-
//
|
|
142
|
+
// NavigationApplication leaves the load of React Native to the template's call: without it the app closes on start,
|
|
143
|
+
// and so it does with a second load of the app's own next to it.
|
|
128
144
|
const application = project.mainApplication && fs.readFileSync(project.mainApplication, 'utf8');
|
|
129
|
-
if (
|
|
130
|
-
application
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
if (application && /:\s*NavigationApplication\(/.test(application)) {
|
|
146
|
+
const loads = /\bloadReactNative\s*\(/.test(codeOf(application));
|
|
147
|
+
if (!loads) {
|
|
148
|
+
checks.push({
|
|
149
|
+
group: 'app',
|
|
150
|
+
title: 'React Native not loaded',
|
|
151
|
+
status: 'error',
|
|
152
|
+
detail: "MainApplication.kt doesn't call loadReactNative(this) in onCreate",
|
|
153
|
+
fix: SETUP,
|
|
154
|
+
});
|
|
155
|
+
} else if (loadsReactNativeItself(application)) {
|
|
156
|
+
checks.push({
|
|
157
|
+
group: 'app',
|
|
158
|
+
title: 'React Native loads twice',
|
|
159
|
+
status: 'error',
|
|
160
|
+
detail:
|
|
161
|
+
'MainApplication.kt loads React Native itself (SoLoader.init or the New Architecture entry point) next to loadReactNative(this)',
|
|
162
|
+
fix: SETUP,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
141
165
|
}
|
|
142
166
|
const generated = generatedCheck(root, run);
|
|
143
167
|
if (generated) checks.push(generated);
|
|
144
168
|
return checks;
|
|
145
169
|
}
|
|
146
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Whether the main activity handles the changes of its window itself, as the React Native template has it. Without
|
|
173
|
+
* one of them Android recreates the activity on a rotation, a resize, a fold or a light/dark switch: every screen goes
|
|
174
|
+
* with it and the navigation starts again from its root. Nothing without a manifest or a main activity.
|
|
175
|
+
* @param {string | undefined} manifest
|
|
176
|
+
* @returns {Check | undefined}
|
|
177
|
+
*/
|
|
178
|
+
function configChangesCheck(manifest) {
|
|
179
|
+
if (!manifest || !fs.existsSync(manifest)) return undefined;
|
|
180
|
+
const activity = mainActivity(fs.readFileSync(manifest, 'utf8'));
|
|
181
|
+
if (!activity) return undefined;
|
|
182
|
+
const value = /android:configChanges\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(activity.text);
|
|
183
|
+
const declared = (value?.[1] ?? value?.[2] ?? '').split('|').map((change) => change.trim());
|
|
184
|
+
const missing = CONFIG_CHANGES.filter((change) => !declared.includes(change));
|
|
185
|
+
if (missing.length === 0) return undefined;
|
|
186
|
+
return {
|
|
187
|
+
group: 'app',
|
|
188
|
+
title: 'Config changes',
|
|
189
|
+
status: 'warn',
|
|
190
|
+
detail: `the main activity doesn't handle ${missing.join(', ')}: Android recreates it, and every screen with it`,
|
|
191
|
+
fix: { label: `Set android:configChanges="${TEMPLATE_CONFIG_CHANGES}" on the main activity` },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
147
195
|
/**
|
|
148
196
|
* One line for a few files: set up, or which need setup or a change by hand.
|
|
149
197
|
* @param {string} title
|
package/cli/migrate/native.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
+
const { withReactNativeLoaded } = require('../setup/steps/mainApplication');
|
|
6
7
|
|
|
7
8
|
/** @typedef {import('../changes').FileChange} FileChange */
|
|
8
9
|
/** @typedef {import('./report').ReportItem} ReportItem */
|
|
@@ -158,6 +159,23 @@ function planNative(project) {
|
|
|
158
159
|
const before = fs.readFileSync(file, 'utf8');
|
|
159
160
|
let next = before;
|
|
160
161
|
for (const [pattern, replacement] of rewrite.rules) next = next.replace(pattern, replacement);
|
|
162
|
+
// react-native-navigation loaded React Native itself; the navigation's application leaves it to the template's
|
|
163
|
+
// loadReactNative(this), which such an app doesn't have.
|
|
164
|
+
if (file === project.mainApplication && /:\s*NavigationApplication\(/.test(next)) {
|
|
165
|
+
const loaded = withReactNativeLoaded(next);
|
|
166
|
+
if (loaded !== undefined) next = loaded;
|
|
167
|
+
else {
|
|
168
|
+
report.push({
|
|
169
|
+
file: path.relative(root, file),
|
|
170
|
+
line: 1,
|
|
171
|
+
step: 'native',
|
|
172
|
+
before: 'onCreate',
|
|
173
|
+
after: 'super.onCreate() then loadReactNative(this), and no load of its own',
|
|
174
|
+
what: 'React Native loads through loadReactNative(this) in onCreate: add it by hand',
|
|
175
|
+
docs: `${DOCS}#native`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
161
179
|
if (next !== before) {
|
|
162
180
|
changes.push({
|
|
163
181
|
name: path.basename(file),
|
package/cli/setup/source.js
CHANGED
|
@@ -35,7 +35,8 @@ function removeUnusedImports(text, names) {
|
|
|
35
35
|
for (const name of names) {
|
|
36
36
|
const line = new RegExp(`^import\\s+${escape(name)}[ \\t]*\\r?\\n`, 'm');
|
|
37
37
|
const simple = name.slice(name.lastIndexOf('.') + 1);
|
|
38
|
-
|
|
38
|
+
// A name in a comment is no use of the import.
|
|
39
|
+
const code = codeOf(result).replace(/^import\s.*$/gm, '');
|
|
39
40
|
if (line.test(result) && !new RegExp(`\\b${escape(simple)}\\b`).test(code))
|
|
40
41
|
result = result.replace(line, '');
|
|
41
42
|
}
|