expo-router 7.0.0-canary-20251216-3f01dbf → 7.0.0-canary-20251223-b83b31e

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 (124) hide show
  1. package/android/build.gradle +2 -2
  2. package/build/color/index.d.ts +44 -0
  3. package/build/color/index.d.ts.map +1 -1
  4. package/build/color/index.js +69 -5
  5. package/build/color/index.js.map +1 -1
  6. package/build/exports.d.ts +1 -0
  7. package/build/exports.d.ts.map +1 -1
  8. package/build/exports.js +3 -1
  9. package/build/exports.js.map +1 -1
  10. package/build/layouts/StackClient.d.ts +1 -1
  11. package/build/layouts/stack-utils/StackHeaderButton.d.ts +77 -2
  12. package/build/layouts/stack-utils/StackHeaderButton.d.ts.map +1 -1
  13. package/build/layouts/stack-utils/StackHeaderButton.js +3 -0
  14. package/build/layouts/stack-utils/StackHeaderButton.js.map +1 -1
  15. package/build/layouts/stack-utils/StackHeaderLeftRight.d.ts.map +1 -1
  16. package/build/layouts/stack-utils/StackHeaderLeftRight.js +7 -5
  17. package/build/layouts/stack-utils/StackHeaderLeftRight.js.map +1 -1
  18. package/build/layouts/stack-utils/StackHeaderMenu.d.ts +83 -7
  19. package/build/layouts/stack-utils/StackHeaderMenu.d.ts.map +1 -1
  20. package/build/layouts/stack-utils/StackHeaderMenu.js +14 -4
  21. package/build/layouts/stack-utils/StackHeaderMenu.js.map +1 -1
  22. package/build/layouts/stack-utils/StackHeaderSpacer.d.ts +7 -1
  23. package/build/layouts/stack-utils/StackHeaderSpacer.d.ts.map +1 -1
  24. package/build/layouts/stack-utils/StackHeaderSpacer.js +5 -2
  25. package/build/layouts/stack-utils/StackHeaderSpacer.js.map +1 -1
  26. package/build/layouts/stack-utils/{StackHeaderItem.d.ts → StackHeaderView.d.ts} +18 -5
  27. package/build/layouts/stack-utils/StackHeaderView.d.ts.map +1 -0
  28. package/build/layouts/stack-utils/{StackHeaderItem.js → StackHeaderView.js} +14 -10
  29. package/build/layouts/stack-utils/StackHeaderView.js.map +1 -0
  30. package/build/layouts/stack-utils/index.d.ts +3 -3
  31. package/build/layouts/stack-utils/index.d.ts.map +1 -1
  32. package/build/layouts/stack-utils/index.js +4 -4
  33. package/build/layouts/stack-utils/index.js.map +1 -1
  34. package/build/layouts/stack-utils/shared.d.ts +4 -42
  35. package/build/layouts/stack-utils/shared.d.ts.map +1 -1
  36. package/build/layouts/stack-utils/shared.js +3 -22
  37. package/build/layouts/stack-utils/shared.js.map +1 -1
  38. package/build/link/ExpoLink.d.ts.map +1 -1
  39. package/build/link/ExpoLink.js +1 -8
  40. package/build/link/ExpoLink.js.map +1 -1
  41. package/build/link/elements.d.ts +36 -13
  42. package/build/link/elements.d.ts.map +1 -1
  43. package/build/link/elements.js +14 -5
  44. package/build/link/elements.js.map +1 -1
  45. package/build/link/preview/native.d.ts +12 -1
  46. package/build/link/preview/native.d.ts.map +1 -1
  47. package/build/link/preview/native.js.map +1 -1
  48. package/build/navigationEvents/index.d.ts +36 -0
  49. package/build/navigationEvents/index.d.ts.map +1 -0
  50. package/build/navigationEvents/index.js +53 -0
  51. package/build/navigationEvents/index.js.map +1 -0
  52. package/build/toolbar/elements.d.ts +325 -16
  53. package/build/toolbar/elements.d.ts.map +1 -1
  54. package/build/toolbar/elements.js +130 -12
  55. package/build/toolbar/elements.js.map +1 -1
  56. package/build/toolbar/index.d.ts +38 -6
  57. package/build/toolbar/index.d.ts.map +1 -1
  58. package/build/toolbar/index.js +36 -1
  59. package/build/toolbar/index.js.map +1 -1
  60. package/build/toolbar/native.ios.d.ts.map +1 -1
  61. package/build/toolbar/native.ios.js.map +1 -1
  62. package/build/toolbar/native.types.d.ts +6 -5
  63. package/build/toolbar/native.types.d.ts.map +1 -1
  64. package/build/toolbar/native.types.js.map +1 -1
  65. package/build/useScreens.d.ts.map +1 -1
  66. package/build/useScreens.js +50 -0
  67. package/build/useScreens.js.map +1 -1
  68. package/build/utils/font.d.ts +9 -0
  69. package/build/utils/font.d.ts.map +1 -0
  70. package/build/utils/font.js +20 -0
  71. package/build/utils/font.js.map +1 -0
  72. package/expo-module.config.json +1 -1
  73. package/ios/LinkPreview/LinkPreviewNativeActionView.swift +105 -24
  74. package/ios/LinkPreview/LinkPreviewNativeModule.swift +35 -8
  75. package/ios/LinkPreview/LinkPreviewNativeNavigation.swift +16 -20
  76. package/ios/LinkPreview/LinkPreviewNativePreviewView.swift +1 -8
  77. package/ios/LinkPreview/LinkPreviewNativeView.swift +48 -50
  78. package/ios/LinkPreview/LinkZoomTransition.swift +8 -10
  79. package/ios/RouterViewWithLogger.swift +5 -0
  80. package/ios/Toolbar/RouterFontUtils.swift +50 -0
  81. package/ios/Toolbar/RouterToolbarHostView.swift +41 -17
  82. package/ios/Toolbar/RouterToolbarItemView.swift +30 -13
  83. package/ios/Toolbar/RouterToolbarModule.swift +28 -0
  84. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.aar +0 -0
  85. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.aar.md5 +1 -0
  86. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.aar.sha1 +1 -0
  87. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.aar.sha256 +1 -0
  88. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.aar.sha512 +1 -0
  89. package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.module → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.module} +17 -17
  90. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.module.md5 +1 -0
  91. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.module.sha1 +1 -0
  92. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.module.sha256 +1 -0
  93. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.module.sha512 +1 -0
  94. package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.pom → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.pom} +1 -1
  95. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.pom.md5 +1 -0
  96. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.pom.sha1 +1 -0
  97. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.pom.sha256 +1 -0
  98. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e.pom.sha512 +1 -0
  99. package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml +4 -4
  100. package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.md5 +1 -1
  101. package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha1 +1 -1
  102. package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha256 +1 -1
  103. package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha512 +1 -1
  104. package/package.json +10 -10
  105. package/build/layouts/stack-utils/StackHeaderItem.d.ts.map +0 -1
  106. package/build/layouts/stack-utils/StackHeaderItem.js.map +0 -1
  107. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.aar +0 -0
  108. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.aar.md5 +0 -1
  109. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.aar.sha1 +0 -1
  110. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.aar.sha256 +0 -1
  111. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.aar.sha512 +0 -1
  112. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.module.md5 +0 -1
  113. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.module.sha1 +0 -1
  114. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.module.sha256 +0 -1
  115. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.module.sha512 +0 -1
  116. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.pom.md5 +0 -1
  117. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.pom.sha1 +0 -1
  118. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.pom.sha256 +0 -1
  119. package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf.pom.sha512 +0 -1
  120. /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf-sources.jar → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e-sources.jar} +0 -0
  121. /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf-sources.jar.md5 → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e-sources.jar.md5} +0 -0
  122. /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf-sources.jar.sha1 → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e-sources.jar.sha1} +0 -0
  123. /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf-sources.jar.sha256 → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e-sources.jar.sha256} +0 -0
  124. /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251216-3f01dbf/expo.modules.router-7.0.0-canary-20251216-3f01dbf-sources.jar.sha512 → 7.0.0-canary-20251223-b83b31e/expo.modules.router-7.0.0-canary-20251223-b83b31e-sources.jar.sha512} +0 -0
@@ -8,8 +8,8 @@ struct TabChangeCommand {
8
8
  }
9
9
 
10
10
  internal class LinkPreviewNativeNavigation {
11
- private var preloadedScreenView: RNSScreenView?
12
- private var preloadedStackView: RNSScreenStackView?
11
+ private weak var preloadedScreenView: RNSScreenView?
12
+ private weak var preloadedStackView: RNSScreenStackView?
13
13
  private var tabChangeCommands: [TabChangeCommand] = []
14
14
  private let logger: Logger?
15
15
 
@@ -27,7 +27,7 @@ internal class LinkPreviewNativeNavigation {
27
27
  // If there were, the preview transition could be to a different tab only
28
28
  if self.tabChangeCommands.isEmpty {
29
29
  logger?.warn(
30
- "expo-router: No preloaded screen view to push. You should only use Link.Preview when navigating inside a stack or native tabs navigator."
30
+ "[expo-router] No preloaded screen view to push. Link.Preview transition is only supported inside a native stack or native tabs navigators."
31
31
  )
32
32
  }
33
33
  return
@@ -45,22 +45,19 @@ internal class LinkPreviewNativeNavigation {
45
45
  let oldTabKeys = tabPath?.path.map { $0.oldTabKey } ?? []
46
46
  let stackOrTabView = findStackViewWithScreenIdOrTabBarController(
47
47
  screenId: screenId, tabKeys: oldTabKeys, responder: responder)
48
- if stackOrTabView != nil {
49
- if let tabView = stackOrTabView as? RNSBottomTabsScreenComponentView {
50
- let newTabKeys = tabPath?.path.map { $0.newTabKey } ?? []
51
- // The order is important here. findStackViewWithScreenIdInSubViews must be called
52
- // even if screenId is nil to compute the tabChangeCommands.
53
- if let stackView = findStackViewWithScreenIdInSubViews(
54
- screenId: screenId, tabKeys: newTabKeys, rootView: tabView), let screenId {
55
- setPreloadedView(stackView: stackView, screenId: screenId)
56
- }
57
- } else if let stackView = stackOrTabView as? RNSScreenStackView, let screenId {
48
+ guard let stackOrTabView else {
49
+ return
50
+ }
51
+ if let tabView = stackOrTabView as? RNSBottomTabsScreenComponentView {
52
+ let newTabKeys = tabPath?.path.map { $0.newTabKey } ?? []
53
+ // The order is important here. findStackViewWithScreenIdInSubViews must be called
54
+ // even if screenId is nil to compute the tabChangeCommands.
55
+ if let stackView = findStackViewWithScreenIdInSubViews(
56
+ screenId: screenId, tabKeys: newTabKeys, rootView: tabView), let screenId {
58
57
  setPreloadedView(stackView: stackView, screenId: screenId)
59
58
  }
60
- } else {
61
- logger?.warn(
62
- "expo-router: No view found for link preview navigation. You should only use Link.Preview when navigating inside a stack or native tabs navigator."
63
- )
59
+ } else if let stackView = stackOrTabView as? RNSScreenStackView, let screenId {
60
+ setPreloadedView(stackView: stackView, screenId: screenId)
64
61
  }
65
62
  }
66
63
 
@@ -88,8 +85,6 @@ internal class LinkPreviewNativeNavigation {
88
85
  // The first and only child of the inner screen stack should be
89
86
  // RNSScreenView (<ScreenStackItem>).
90
87
  let screenContentView = innerScreenStack.reactSubviews().first as? RNSScreenView {
91
- print("screenView screenId:", screenView.screenId)
92
- print("innerScreenStack screenIds:", innerScreenStack.screenIds)
93
88
  // Same as above, we let React Native Screens handle the transition.
94
89
  // We need to set the activity of inner screen as well, because its
95
90
  // react value is the same as the preloaded screen - 0.
@@ -152,7 +147,8 @@ internal class LinkPreviewNativeNavigation {
152
147
  ) -> (tabIndex: Int, tabView: UIView)? {
153
148
  let views = tabBarController.viewControllers?.compactMap { $0.view } ?? []
154
149
  let enumeratedViews = views.enumerated()
155
- if let result = enumeratedViews
150
+ if let result =
151
+ enumeratedViews
156
152
  .first(where: { _, view in
157
153
  guard let tabView = view as? RNSBottomTabsScreenComponentView, let tabKey = tabView.tabKey
158
154
  else {
@@ -1,16 +1,9 @@
1
1
  import ExpoModulesCore
2
- import WebKit
3
2
 
4
- class NativeLinkPreviewContentView: ExpoView {
3
+ class NativeLinkPreviewContentView: RouterViewWithLogger {
5
4
  var preferredContentSize: CGSize = .zero
6
5
 
7
- required init(appContext: AppContext? = nil) {
8
- super.init(appContext: appContext)
9
- }
10
-
11
6
  func setInitialSize(bounds: CGRect) {
12
- #if RCT_NEW_ARCH_ENABLED
13
7
  self.setShadowNodeSize(Float(bounds.width), height: Float(bounds.height))
14
- #endif
15
8
  }
16
9
  }
@@ -1,7 +1,7 @@
1
1
  import ExpoModulesCore
2
2
  import RNScreens
3
3
 
4
- class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
4
+ class NativeLinkPreviewView: RouterViewWithLogger, UIContextMenuInteractionDelegate,
5
5
  RNSDismissibleModalProtocol, LinkPreviewMenuUpdatable {
6
6
  private var preview: NativeLinkPreviewContentView?
7
7
  private var interaction: UIContextMenuInteraction?
@@ -19,7 +19,7 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
19
19
  private var actions: [LinkPreviewNativeActionView] = []
20
20
 
21
21
  private lazy var linkPreviewNativeNavigation: LinkPreviewNativeNavigation = {
22
- return LinkPreviewNativeNavigation(logger: appContext?.jsLogger)
22
+ return LinkPreviewNativeNavigation(logger: logger)
23
23
  }()
24
24
 
25
25
  let onPreviewTapped = EventDispatcher()
@@ -53,64 +53,62 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
53
53
  }
54
54
 
55
55
  // MARK: - Children
56
- #if RCT_NEW_ARCH_ENABLED
57
- override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
58
- if let previewView = childComponentView as? NativeLinkPreviewContentView {
59
- preview = previewView
60
- } else if let actionView = childComponentView as? LinkPreviewNativeActionView {
61
- actionView.parentMenuUpdatable = self
62
- actions.append(actionView)
63
- } else {
64
- if directChild != nil {
65
- print(
66
- "[expo-router] Found a second child of <Link.Trigger>. Only one is allowed. This is most likely a bug in expo-router."
56
+ override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
57
+ if let previewView = childComponentView as? NativeLinkPreviewContentView {
58
+ preview = previewView
59
+ } else if let actionView = childComponentView as? LinkPreviewNativeActionView {
60
+ actionView.parentMenuUpdatable = self
61
+ actions.append(actionView)
62
+ } else {
63
+ if directChild != nil {
64
+ logger?.warn(
65
+ "[expo-router] Found a second child of <Link.Trigger>. Only one is allowed. This is most likely a bug in expo-router."
66
+ )
67
+ return
68
+ }
69
+ directChild = childComponentView
70
+ if let interaction = self.interaction {
71
+ if let indirectTrigger = childComponentView as? LinkPreviewIndirectTriggerProtocol {
72
+ indirectTrigger.indirectTrigger?.addInteraction(interaction)
73
+ } else {
74
+ childComponentView.addInteraction(interaction)
75
+ }
76
+ }
77
+ super.mountChildComponentView(childComponentView, index: index)
78
+ }
79
+ }
80
+
81
+ override func unmountChildComponentView(_ child: UIView, index: Int) {
82
+ if child is NativeLinkPreviewContentView {
83
+ preview = nil
84
+ } else if let actionView = child as? LinkPreviewNativeActionView {
85
+ actions.removeAll(where: {
86
+ $0 == actionView
87
+ })
88
+ } else {
89
+ if let directChild = directChild {
90
+ if directChild != child {
91
+ logger?.warn(
92
+ "[expo-router] Unmounting unexpected child from <Link.Trigger>. This is most likely a bug in expo-router."
67
93
  )
68
94
  return
69
95
  }
70
- directChild = childComponentView
71
96
  if let interaction = self.interaction {
72
- if let indirectTrigger = childComponentView as? LinkPreviewIndirectTriggerProtocol {
73
- indirectTrigger.indirectTrigger?.addInteraction(interaction)
97
+ if let indirectTrigger = directChild as? LinkPreviewIndirectTriggerProtocol {
98
+ indirectTrigger.indirectTrigger?.removeInteraction(interaction)
74
99
  } else {
75
- childComponentView.addInteraction(interaction)
100
+ directChild.removeInteraction(interaction)
76
101
  }
77
102
  }
78
- super.mountChildComponentView(childComponentView, index: index)
79
- }
80
- }
81
-
82
- override func unmountChildComponentView(_ child: UIView, index: Int) {
83
- if child is NativeLinkPreviewContentView {
84
- preview = nil
85
- } else if let actionView = child as? LinkPreviewNativeActionView {
86
- actions.removeAll(where: {
87
- $0 == actionView
88
- })
103
+ super.unmountChildComponentView(child, index: index)
89
104
  } else {
90
- if let directChild = directChild {
91
- if directChild != child {
92
- print(
93
- "[expo-router] Unmounting unexpected child from <Link.Trigger>. This is most likely a bug in expo-router."
94
- )
95
- return
96
- }
97
- if let interaction = self.interaction {
98
- if let indirectTrigger = directChild as? LinkPreviewIndirectTriggerProtocol {
99
- indirectTrigger.indirectTrigger?.removeInteraction(interaction)
100
- } else {
101
- directChild.removeInteraction(interaction)
102
- }
103
- }
104
- super.unmountChildComponentView(child, index: index)
105
- } else {
106
- print(
107
- "[expo-router] No link child found to unmount. This is most likely a bug in expo-router."
108
- )
109
- return
110
- }
105
+ logger?.warn(
106
+ "[expo-router] No link child found to unmount. This is most likely a bug in expo-router."
107
+ )
108
+ return
111
109
  }
112
110
  }
113
- #endif
111
+ }
114
112
 
115
113
  // MARK: - UIContextMenuInteractionDelegate
116
114
 
@@ -244,7 +244,7 @@ class LinkZoomTransitionAlignmentRectDetector: LinkZoomExpoView {
244
244
  ) {
245
245
  guard child == nil else {
246
246
  logger?.warn(
247
- "[expo-router] Link.AppleZoomTarget can only have a single native child."
247
+ "[expo-router] Link.AppleZoomTarget can only have a single native child. If you passed a single child, consider adding collapsible={false} to your component"
248
248
  )
249
249
  return
250
250
  }
@@ -287,7 +287,7 @@ class LinkZoomTransitionEnabler: LinkZoomExpoView {
287
287
 
288
288
  private func setupZoomTransition() {
289
289
  if self.zoomTransitionSourceIdentifier.isEmpty {
290
- print("[expo-router] No zoomTransitionSourceIdentifier passed to LinkZoomTransitionEnabler")
290
+ logger?.warn("[expo-router] No zoomTransitionSourceIdentifier passed to LinkZoomTransitionEnabler. This is most likely a bug in expo-router.")
291
291
  return
292
292
  }
293
293
  if let controller = self.findViewController() {
@@ -329,8 +329,8 @@ class LinkZoomTransitionEnabler: LinkZoomExpoView {
329
329
  view = linkPreviewView.directChild
330
330
  }
331
331
  guard let view else {
332
- print(
333
- "[expo-router] No source view found for identifier \(self.zoomTransitionSourceIdentifier) to enable zoom transition"
332
+ self.logger?.warn(
333
+ "[expo-router] No source view found for identifier \(self.zoomTransitionSourceIdentifier) to enable zoom transition. This is most likely a bug in expo-router."
334
334
  )
335
335
  return nil
336
336
  }
@@ -339,7 +339,7 @@ class LinkZoomTransitionEnabler: LinkZoomExpoView {
339
339
  return
340
340
  }
341
341
  } else {
342
- print("[expo-router] No navigation controller found to enable zoom transition")
342
+ logger?.warn("[expo-router] No navigation controller found to enable zoom transition. This is most likely a bug in expo-router.")
343
343
  }
344
344
  }
345
345
 
@@ -390,7 +390,7 @@ class LinkZoomTransitionEnabler: LinkZoomExpoView {
390
390
  }
391
391
  }
392
392
 
393
- class LinkZoomExpoView: ExpoView {
393
+ class LinkZoomExpoView: RouterViewWithLogger {
394
394
  var module: LinkPreviewNativeModule? {
395
395
  return appContext?.moduleRegistry.get(moduleWithName: LinkPreviewNativeModule.moduleName)
396
396
  as? LinkPreviewNativeModule
@@ -398,7 +398,7 @@ class LinkZoomExpoView: ExpoView {
398
398
 
399
399
  var sourceRepository: LinkZoomTransitionsSourceRepository? {
400
400
  guard let module else {
401
- print("[expo-router] LinkPreviewNativeModule not loaded")
401
+ logger?.warn("[expo-router] LinkPreviewNativeModule not loaded. Make sure expo-router is properly configured.")
402
402
  return nil
403
403
  }
404
404
  return module.zoomSourceRepository
@@ -406,11 +406,9 @@ class LinkZoomExpoView: ExpoView {
406
406
 
407
407
  var alignmentViewRepository: LinkZoomTransitionsAlignmentViewRepository? {
408
408
  guard let module else {
409
- print("[expo-router] LinkPreviewNativeModule not loaded")
409
+ logger?.warn("[expo-router] LinkPreviewNativeModule not loaded. Make sure expo-router is properly configured.")
410
410
  return nil
411
411
  }
412
412
  return module.zoomAlignmentViewRepository
413
413
  }
414
-
415
- lazy var logger = appContext?.jsLogger
416
414
  }
@@ -0,0 +1,5 @@
1
+ import ExpoModulesCore
2
+
3
+ class RouterViewWithLogger: ExpoView {
4
+ lazy var logger = appContext?.jsLogger
5
+ }
@@ -0,0 +1,50 @@
1
+ import React
2
+ import UIKit
3
+
4
+ struct RouterFontUtils {
5
+ static func convertTitleStyleToFont(_ titleStyle: TitleStyle) -> UIFont {
6
+ let fontFamily = titleStyle.fontFamily
7
+ let fontWeight = titleStyle.fontWeight
8
+
9
+ let resolvedFontSize = resolveFontSize(titleStyle.fontSize)
10
+
11
+ if fontFamily != nil || fontWeight != nil {
12
+ return RCTFont.update(
13
+ nil,
14
+ withFamily: fontFamily,
15
+ size: NSNumber(value: Float(resolvedFontSize)),
16
+ weight: fontWeight,
17
+ style: nil,
18
+ variant: nil,
19
+ scaleMultiplier: 1.0)
20
+ }
21
+ return UIFont.systemFont(ofSize: resolvedFontSize)
22
+ }
23
+
24
+ static func setTitleStyle(fromConfig titleStyle: TitleStyle, for item: UIBarButtonItem) {
25
+ var attrs: [NSAttributedString.Key: Any] = [:]
26
+
27
+ attrs[.font] = convertTitleStyleToFont(titleStyle)
28
+
29
+ if let color = titleStyle.color {
30
+ attrs[.foregroundColor] = color
31
+ }
32
+
33
+ item.setTitleTextAttributes(attrs, for: .normal)
34
+ item.setTitleTextAttributes(attrs, for: .highlighted)
35
+ item.setTitleTextAttributes(attrs, for: .disabled)
36
+ item.setTitleTextAttributes(attrs, for: .selected)
37
+ item.setTitleTextAttributes(attrs, for: .focused)
38
+ }
39
+
40
+ private static func resolveFontSize(_ fontSize: Double?) -> CGFloat {
41
+ if let fontSize = fontSize {
42
+ return CGFloat(fontSize)
43
+ }
44
+ #if os(tvOS)
45
+ return 17.0
46
+ #else
47
+ return UIFont.labelFontSize
48
+ #endif
49
+ }
50
+ }
@@ -2,16 +2,12 @@ import ExpoModulesCore
2
2
  import RNScreens
3
3
  import UIKit
4
4
 
5
- class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
5
+ class RouterToolbarHostView: RouterViewWithLogger, LinkPreviewMenuUpdatable {
6
6
  // Mutable map of toolbar items
7
7
  var toolbarItemsArray: [String] = []
8
8
  var toolbarItemsMap: [String: RouterToolbarItemView] = [:]
9
9
  var menuItemsMap: [String: LinkPreviewNativeActionView] = [:]
10
10
 
11
- required init(appContext: AppContext? = nil) {
12
- super.init(appContext: appContext)
13
- }
14
-
15
11
  private func addRouterToolbarItemAtIndex(
16
12
  _ item: RouterToolbarItemView,
17
13
  index: Int
@@ -56,6 +52,7 @@ class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
56
52
  if let item = toolbarItemsMap[$0] {
57
53
  return item.barButtonItem
58
54
  }
55
+ // TODO: Extract this logic to separate function
59
56
  if let menu = menuItemsMap[$0] {
60
57
  let item = UIBarButtonItem(
61
58
  title: menu.title,
@@ -65,10 +62,35 @@ class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
65
62
  )
66
63
  // Otherwise, the menu items will be reversed in the toolbar
67
64
  item.preferredMenuElementOrder = .fixed
65
+ if #available(iOS 26.0, *) {
66
+ if let hidesSharedBackground = menu.hidesSharedBackground {
67
+ item.hidesSharedBackground = hidesSharedBackground
68
+ }
69
+ if let sharesBackground = menu.sharesBackground {
70
+ item.sharesBackground = sharesBackground
71
+ }
72
+ }
73
+ if let titleStyle = menu.titleStyle {
74
+ RouterFontUtils.setTitleStyle(fromConfig: titleStyle, for: item)
75
+ }
76
+ item.isHidden = menu.routerHidden
77
+ item.isEnabled = !menu.disabled
78
+ if let accessibilityLabel = menu.accessibilityLabelForMenu {
79
+ item.accessibilityLabel = accessibilityLabel
80
+ } else {
81
+ item.accessibilityLabel = menu.title
82
+ }
83
+ if let accessibilityHint = menu.accessibilityHintForMenu {
84
+ item.accessibilityHint = accessibilityHint
85
+ }
86
+ item.tintColor = menu.customTintColor
87
+ if let style = menu.barButtonItemStyle {
88
+ item.style = style
89
+ }
68
90
  return item
69
91
  }
70
- print(
71
- "[expo-router] Warning: Could not find toolbar item or menu for identifier \($0)"
92
+ logger?.warn(
93
+ "[expo-router] Warning: Could not find toolbar item or menu for identifier \($0). This is most likely a bug in expo-router."
72
94
  )
73
95
  return nil
74
96
  }, animated: true)
@@ -76,16 +98,15 @@ class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
76
98
  false, animated: true)
77
99
  return
78
100
  }
79
- } else {
80
- print(
81
- "[expo-router] Warning: Could not find owning UIViewController for RouterToolbarHostView")
82
101
  }
83
102
  }
84
103
 
85
104
  override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
86
105
  if let toolbarItem = childComponentView as? RouterToolbarItemView {
87
106
  if toolbarItem.identifier.isEmpty {
88
- print("[expo-router] RouterToolbarItemView identifier is empty")
107
+ logger?.warn(
108
+ "[expo-router] RouterToolbarItemView identifier is empty. This is most likely a bug in expo-router."
109
+ )
89
110
  return
90
111
  }
91
112
  addRouterToolbarItemAtIndex(toolbarItem, index: index)
@@ -93,8 +114,8 @@ class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
93
114
  menu.parentMenuUpdatable = self
94
115
  addMenuToolbarItemAtIndex(menu, index: index)
95
116
  } else {
96
- print(
97
- "ExpoRouter: Unknown child component view (\(childComponentView)) mounted to RouterToolbarHost"
117
+ logger?.warn(
118
+ "[expo-router] Unknown child component view (\(childComponentView)) mounted to RouterToolbarHost. This is most likely a bug in expo-router."
98
119
  )
99
120
  }
100
121
  updateToolbarItems()
@@ -103,19 +124,22 @@ class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
103
124
  override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
104
125
  if let toolbarItem = childComponentView as? RouterToolbarItemView {
105
126
  if toolbarItem.identifier.isEmpty {
106
- print("[expo-router] RouterToolbarItemView identifier is empty")
127
+ logger?.warn(
128
+ "[expo-router] RouterToolbarItemView identifier is empty. This is most likely a bug in expo-router."
129
+ )
107
130
  return
108
131
  }
109
132
  removeToolbarItemWithId(toolbarItem.identifier)
110
133
  } else if let menu = childComponentView as? LinkPreviewNativeActionView {
111
134
  if menu.identifier.isEmpty {
112
- print("[expo-router] Menu identifier is empty")
135
+ logger?.warn(
136
+ "[expo-router] Menu identifier is empty. This is most likely a bug in expo-router.")
113
137
  return
114
138
  }
115
139
  removeToolbarItemWithId(menu.identifier)
116
140
  } else {
117
- print(
118
- "ExpoRouter: Unknown child component view (\(childComponentView)) unmounted from RouterToolbarHost"
141
+ logger?.warn(
142
+ "[expo-router] Unknown child component view (\(childComponentView)) unmounted from RouterToolbarHost. This is most likely a bug in expo-router."
119
143
  )
120
144
  }
121
145
  updateToolbarItems()
@@ -1,8 +1,7 @@
1
1
  import ExpoModulesCore
2
- import React
3
2
  import UIKit
4
3
 
5
- class RouterToolbarItemView: ExpoView {
4
+ class RouterToolbarItemView: RouterViewWithLogger {
6
5
  var identifier: String = ""
7
6
  @ReactiveProp var type: ItemType?
8
7
  @ReactiveProp var title: String?
@@ -18,6 +17,10 @@ class RouterToolbarItemView: ExpoView {
18
17
  @ReactiveProp var selected: Bool = false
19
18
  @ReactiveProp var possibleTitles: Set<String>?
20
19
  @ReactiveProp var badgeConfiguration: BadgeConfiguration?
20
+ @ReactiveProp var titleStyle: TitleStyle?
21
+ @ReactiveProp var routerAccessibilityLabel: String?
22
+ @ReactiveProp var routerAccessibilityHint: String?
23
+ @ReactiveProp var disabled: Bool = false
21
24
 
22
25
  var host: RouterToolbarHostView?
23
26
 
@@ -50,6 +53,9 @@ class RouterToolbarItemView: ExpoView {
50
53
  if let tintColor = customTintColor {
51
54
  item.tintColor = tintColor
52
55
  }
56
+ if let titleStyle {
57
+ RouterFontUtils.setTitleStyle(fromConfig: titleStyle, for: item)
58
+ }
53
59
  }
54
60
  if #available(iOS 26.0, *) {
55
61
  item.hidesSharedBackground = hidesSharedBackground
@@ -67,6 +73,13 @@ class RouterToolbarItemView: ExpoView {
67
73
  item.isHidden = routerHidden
68
74
  }
69
75
  item.isSelected = selected
76
+ if let routerAccessibilityLabel = routerAccessibilityLabel {
77
+ item.accessibilityLabel = routerAccessibilityLabel
78
+ }
79
+ if let routerAccessibilityHint = routerAccessibilityHint {
80
+ item.accessibilityHint = routerAccessibilityHint
81
+ }
82
+ item.isEnabled = !disabled
70
83
  if #available(iOS 26.0, *) {
71
84
  if let badgeConfig = badgeConfiguration {
72
85
  var badge = UIBarButtonItem.Badge.indicator()
@@ -81,17 +94,14 @@ class RouterToolbarItemView: ExpoView {
81
94
  }
82
95
  if badgeConfig.fontFamily != nil || badgeConfig.fontSize != nil
83
96
  || badgeConfig.fontWeight != nil {
84
- let font = RCTFont.update(
85
- nil,
86
- withFamily: badgeConfig.fontFamily,
87
- size: badgeConfig.fontSize != nil ? NSNumber(value: badgeConfig.fontSize!) : nil,
88
- weight: badgeConfig.fontWeight,
89
- style: nil,
90
- variant: nil,
91
- scaleMultiplier: 1.0)
97
+ let font = RouterFontUtils.convertTitleStyleToFont(
98
+ TitleStyle(
99
+ fontFamily: badgeConfig.fontFamily,
100
+ fontSize: badgeConfig.fontSize,
101
+ fontWeight: badgeConfig.fontWeight
102
+ ))
92
103
  badge.font = font
93
104
  }
94
- // TODO: Find out why this does not work
95
105
  item.badge = badge
96
106
  }
97
107
  }
@@ -105,8 +115,8 @@ class RouterToolbarItemView: ExpoView {
105
115
 
106
116
  override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
107
117
  if customView != nil {
108
- print(
109
- "[expo-router] Warning: RouterToolbarItemView can only have one child view"
118
+ logger?.warn(
119
+ "[expo-router] RouterToolbarItemView can only have one child view. This is most likely a bug in expo-router."
110
120
  )
111
121
  return
112
122
  }
@@ -138,6 +148,13 @@ struct BadgeConfiguration: Equatable {
138
148
  var fontWeight: String?
139
149
  }
140
150
 
151
+ struct TitleStyle: Equatable {
152
+ var fontFamily: String?
153
+ var fontSize: Double?
154
+ var fontWeight: String?
155
+ var color: UIColor?
156
+ }
157
+
141
158
  @propertyWrapper
142
159
  struct ReactiveProp<Value: Equatable> {
143
160
  private var value: Value
@@ -57,6 +57,18 @@ public class RouterToolbarModule: Module {
57
57
  (view: RouterToolbarItemView, config: BadgeConfigurationRecord?) in
58
58
  view.badgeConfiguration = config?.toBadgeConfiguration()
59
59
  }
60
+ Prop("titleStyle") { (view: RouterToolbarItemView, style: TitleStyleRecord?) in
61
+ view.titleStyle = style?.toTitleStyle()
62
+ }
63
+ Prop("accessibilityLabel") { (view: RouterToolbarItemView, accessibilityLabel: String?) in
64
+ view.accessibilityLabel = accessibilityLabel
65
+ }
66
+ Prop("accessibilityHint") { (view: RouterToolbarItemView, accessibilityHint: String?) in
67
+ view.accessibilityHint = accessibilityHint
68
+ }
69
+ Prop("disabled") { (view: RouterToolbarItemView, disabled: Bool?) in
70
+ view.disabled = disabled ?? false
71
+ }
60
72
 
61
73
  Events("onSelected")
62
74
  }
@@ -100,3 +112,19 @@ struct BadgeConfigurationRecord: Record {
100
112
  )
101
113
  }
102
114
  }
115
+
116
+ struct TitleStyleRecord: Record {
117
+ @Field var fontFamily: String?
118
+ @Field var fontSize: Double?
119
+ @Field var fontWeight: String?
120
+ @Field var color: UIColor?
121
+
122
+ func toTitleStyle() -> TitleStyle {
123
+ return TitleStyle(
124
+ fontFamily: fontFamily,
125
+ fontSize: fontSize,
126
+ fontWeight: fontWeight,
127
+ color: color
128
+ )
129
+ }
130
+ }
@@ -0,0 +1 @@
1
+ 0c996832cda246cbf02acf875f7c3add8610a0e5e7c466bbe5fa2f073a6a644933eec90d1bff2a7fac7fd3c0c5bdae8a695373cebdec4f9853ce116658960554