expo-router 5.2.0-canary-20250722-599a28f → 5.2.0-canary-20250811-5c940c0

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 (192) hide show
  1. package/_ctx.android.js +1 -1
  2. package/_ctx.ios.js +1 -1
  3. package/_ctx.web.js +1 -1
  4. package/assets/modal.module.css +12 -3
  5. package/build/ExpoRoot.d.ts.map +1 -1
  6. package/build/ExpoRoot.js +12 -2
  7. package/build/ExpoRoot.js.map +1 -1
  8. package/build/Route.d.ts +9 -0
  9. package/build/Route.d.ts.map +1 -1
  10. package/build/Route.js.map +1 -1
  11. package/build/constants.d.ts +2 -0
  12. package/build/constants.d.ts.map +1 -1
  13. package/build/constants.js +3 -1
  14. package/build/constants.js.map +1 -1
  15. package/build/doctor/index.d.ts +1 -1
  16. package/build/doctor/index.d.ts.map +1 -1
  17. package/build/doctor/index.js +4 -1
  18. package/build/doctor/index.js.map +1 -1
  19. package/build/exports.d.ts +2 -2
  20. package/build/exports.d.ts.map +1 -1
  21. package/build/exports.js +6 -6
  22. package/build/exports.js.map +1 -1
  23. package/build/fork/native-stack/createNativeStackNavigator.d.ts +2 -2
  24. package/build/fork/native-stack/createNativeStackNavigator.d.ts.map +1 -1
  25. package/build/fork/native-stack/createNativeStackNavigator.js +82 -2
  26. package/build/fork/native-stack/createNativeStackNavigator.js.map +1 -1
  27. package/build/fork/useLinking.js +2 -2
  28. package/build/fork/useLinking.js.map +1 -1
  29. package/build/getLinkingConfig.d.ts +29 -2
  30. package/build/getLinkingConfig.d.ts.map +1 -1
  31. package/build/getLinkingConfig.js +35 -4
  32. package/build/getLinkingConfig.js.map +1 -1
  33. package/build/getRoutes.d.ts.map +1 -1
  34. package/build/getRoutes.js +5 -2
  35. package/build/getRoutes.js.map +1 -1
  36. package/build/getRoutesCore.d.ts +1 -0
  37. package/build/getRoutesCore.d.ts.map +1 -1
  38. package/build/getRoutesCore.js +66 -1
  39. package/build/getRoutesCore.js.map +1 -1
  40. package/build/getRoutesSSR.d.ts.map +1 -1
  41. package/build/getRoutesSSR.js +14 -4
  42. package/build/getRoutesSSR.js.map +1 -1
  43. package/build/getServerManifest.d.ts +20 -1
  44. package/build/getServerManifest.d.ts.map +1 -1
  45. package/build/getServerManifest.js +8 -1
  46. package/build/getServerManifest.js.map +1 -1
  47. package/build/global-state/routeInfo.d.ts.map +1 -1
  48. package/build/global-state/routeInfo.js +12 -1
  49. package/build/global-state/routeInfo.js.map +1 -1
  50. package/build/global-state/router-store.d.ts.map +1 -1
  51. package/build/global-state/router-store.js +4 -0
  52. package/build/global-state/router-store.js.map +1 -1
  53. package/build/global-state/routing.d.ts +29 -1
  54. package/build/global-state/routing.d.ts.map +1 -1
  55. package/build/global-state/routing.js +78 -42
  56. package/build/global-state/routing.js.map +1 -1
  57. package/build/global-state/utils.d.ts +4 -0
  58. package/build/global-state/utils.d.ts.map +1 -0
  59. package/build/global-state/utils.js +29 -0
  60. package/build/global-state/utils.js.map +1 -0
  61. package/build/hooks.d.ts +1 -1
  62. package/build/hooks.d.ts.map +1 -1
  63. package/build/hooks.js +9 -4
  64. package/build/hooks.js.map +1 -1
  65. package/build/layouts/DrawerClient.d.ts +2 -2
  66. package/build/layouts/StackClient.d.ts +2 -2
  67. package/build/layouts/StackClient.d.ts.map +1 -1
  68. package/build/layouts/StackClient.js +11 -6
  69. package/build/layouts/StackClient.js.map +1 -1
  70. package/build/layouts/TabsClient.d.ts +3 -3
  71. package/build/layouts/withLayoutContext.d.ts.map +1 -1
  72. package/build/layouts/withLayoutContext.js +13 -0
  73. package/build/layouts/withLayoutContext.js.map +1 -1
  74. package/build/link/ExpoLink.d.ts.map +1 -1
  75. package/build/link/ExpoLink.js +3 -2
  76. package/build/link/ExpoLink.js.map +1 -1
  77. package/build/link/InternalLinkPreviewContext.d.ts +6 -0
  78. package/build/link/InternalLinkPreviewContext.d.ts.map +1 -0
  79. package/build/link/InternalLinkPreviewContext.js +6 -0
  80. package/build/link/InternalLinkPreviewContext.js.map +1 -0
  81. package/build/link/Link.d.ts +2 -67
  82. package/build/link/Link.d.ts.map +1 -1
  83. package/build/link/Link.js +5 -70
  84. package/build/link/Link.js.map +1 -1
  85. package/build/link/LinkWithPreview.d.ts +1 -46
  86. package/build/link/LinkWithPreview.d.ts.map +1 -1
  87. package/build/link/LinkWithPreview.js +20 -113
  88. package/build/link/LinkWithPreview.js.map +1 -1
  89. package/build/link/elements.d.ts +166 -0
  90. package/build/link/elements.d.ts.map +1 -0
  91. package/build/link/elements.js +172 -0
  92. package/build/link/elements.js.map +1 -0
  93. package/build/link/preview/HrefPreview.d.ts +1 -1
  94. package/build/link/preview/HrefPreview.d.ts.map +1 -1
  95. package/build/link/preview/HrefPreview.js +43 -7
  96. package/build/link/preview/HrefPreview.js.map +1 -1
  97. package/build/link/preview/LinkPreviewContext.d.ts +3 -2
  98. package/build/link/preview/LinkPreviewContext.d.ts.map +1 -1
  99. package/build/link/preview/LinkPreviewContext.js +3 -2
  100. package/build/link/preview/LinkPreviewContext.js.map +1 -1
  101. package/build/link/preview/native.d.ts +14 -6
  102. package/build/link/preview/native.d.ts.map +1 -1
  103. package/build/link/preview/native.js +5 -4
  104. package/build/link/preview/native.js.map +1 -1
  105. package/build/link/preview/useNextScreenId.d.ts +8 -1
  106. package/build/link/preview/useNextScreenId.d.ts.map +1 -1
  107. package/build/link/preview/useNextScreenId.js +37 -32
  108. package/build/link/preview/useNextScreenId.js.map +1 -1
  109. package/build/link/preview/utils.d.ts +12 -0
  110. package/build/link/preview/utils.d.ts.map +1 -0
  111. package/build/link/preview/utils.js +66 -0
  112. package/build/link/preview/utils.js.map +1 -0
  113. package/build/modal/Modal.d.ts +9 -0
  114. package/build/modal/Modal.d.ts.map +1 -1
  115. package/build/modal/Modal.js +12 -2
  116. package/build/modal/Modal.js.map +1 -1
  117. package/build/modal/ModalsRenderer.js +1 -1
  118. package/build/modal/ModalsRenderer.js.map +1 -1
  119. package/build/modal/web/ModalStackRouteDrawer.web.d.ts.map +1 -1
  120. package/build/modal/web/ModalStackRouteDrawer.web.js +0 -1
  121. package/build/modal/web/ModalStackRouteDrawer.web.js.map +1 -1
  122. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsNavigator.d.ts +25 -0
  123. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsNavigator.d.ts.map +1 -0
  124. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsNavigator.js +27 -0
  125. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsNavigator.js.map +1 -0
  126. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsRouter.d.ts +3 -0
  127. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsRouter.d.ts.map +1 -0
  128. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsRouter.js +65 -0
  129. package/build/native-tabs/NativeBottomTabs/NativeBottomTabsRouter.js.map +1 -0
  130. package/build/native-tabs/NativeBottomTabs/NativeTabs.d.ts +7 -0
  131. package/build/native-tabs/NativeBottomTabs/NativeTabs.d.ts.map +1 -0
  132. package/build/native-tabs/NativeBottomTabs/NativeTabs.js +9 -0
  133. package/build/native-tabs/NativeBottomTabs/NativeTabs.js.map +1 -0
  134. package/build/native-tabs/NativeBottomTabs/NativeTabsView.d.ts +103 -0
  135. package/build/native-tabs/NativeBottomTabs/NativeTabsView.d.ts.map +1 -0
  136. package/build/native-tabs/NativeBottomTabs/NativeTabsView.js +69 -0
  137. package/build/native-tabs/NativeBottomTabs/NativeTabsView.js.map +1 -0
  138. package/build/native-tabs/NativeBottomTabs/TabOptions.d.ts +40 -0
  139. package/build/native-tabs/NativeBottomTabs/TabOptions.d.ts.map +1 -0
  140. package/build/native-tabs/NativeBottomTabs/TabOptions.js +103 -0
  141. package/build/native-tabs/NativeBottomTabs/TabOptions.js.map +1 -0
  142. package/build/native-tabs/NativeBottomTabs/utils.d.ts +7 -0
  143. package/build/native-tabs/NativeBottomTabs/utils.d.ts.map +1 -0
  144. package/build/native-tabs/NativeBottomTabs/utils.js +21 -0
  145. package/build/native-tabs/NativeBottomTabs/utils.js.map +1 -0
  146. package/build/native-tabs/common/elements.d.ts +59 -0
  147. package/build/native-tabs/common/elements.d.ts.map +1 -0
  148. package/build/native-tabs/common/elements.js +15 -0
  149. package/build/native-tabs/common/elements.js.map +1 -0
  150. package/build/native-tabs/index.d.ts +3 -0
  151. package/build/native-tabs/index.d.ts.map +1 -0
  152. package/build/native-tabs/index.js +10 -0
  153. package/build/native-tabs/index.js.map +1 -0
  154. package/build/routes-manifest.d.ts +42 -0
  155. package/build/routes-manifest.d.ts.map +1 -1
  156. package/build/routes-manifest.js.map +1 -1
  157. package/build/testing-library/mock-config.d.ts +18 -0
  158. package/build/testing-library/mock-config.d.ts.map +1 -1
  159. package/build/testing-library/mock-config.js +4 -1
  160. package/build/testing-library/mock-config.js.map +1 -1
  161. package/build/ui/common.d.ts.map +1 -1
  162. package/build/ui/common.js +7 -6
  163. package/build/ui/common.js.map +1 -1
  164. package/build/useNavigation.d.ts.map +1 -1
  165. package/build/useNavigation.js +8 -5
  166. package/build/useNavigation.js.map +1 -1
  167. package/build/views/NoSSR.d.ts +5 -0
  168. package/build/views/NoSSR.d.ts.map +1 -0
  169. package/build/views/NoSSR.js +22 -0
  170. package/build/views/NoSSR.js.map +1 -0
  171. package/build/views/Screen.d.ts.map +1 -1
  172. package/build/views/Screen.js +4 -1
  173. package/build/views/Screen.js.map +1 -1
  174. package/build/views/Sitemap.d.ts.map +1 -1
  175. package/build/views/Sitemap.js +75 -2
  176. package/build/views/Sitemap.js.map +1 -1
  177. package/build/views/Unmatched.d.ts.map +1 -1
  178. package/build/views/Unmatched.js +14 -4
  179. package/build/views/Unmatched.js.map +1 -1
  180. package/ios/ExpoHead.podspec +10 -1
  181. package/ios/LinkPreview/LinkPreviewNativeActionView.swift +159 -19
  182. package/ios/LinkPreview/LinkPreviewNativeModule.swift +37 -5
  183. package/ios/LinkPreview/LinkPreviewNativeNavigation.h +37 -11
  184. package/ios/LinkPreview/LinkPreviewNativeNavigation.mm +110 -87
  185. package/ios/LinkPreview/LinkPreviewNativeNavigation.swift +136 -0
  186. package/ios/LinkPreview/LinkPreviewNativePreviewView.swift +2 -0
  187. package/ios/LinkPreview/LinkPreviewNativeView.swift +70 -69
  188. package/package.json +38 -9
  189. package/plugin/build/index.d.ts +2 -0
  190. package/plugin/options.json +5 -0
  191. package/plugin/src/index.ts +2 -0
  192. package/server.d.ts +2 -1
@@ -1,34 +1,174 @@
1
1
  import ExpoModulesCore
2
2
  import WebKit
3
3
 
4
- class LinkPreviewNativeActionView: ExpoView {
5
- var id: String = ""
6
- var title: String = ""
7
- var icon: String?
8
- var subActions: [LinkPreviewNativeActionView] = []
4
+ class LinkPreviewNativeActionView: ExpoView, LinkPreviewMenuUpdatable {
5
+ // MARK: - Shared props
6
+ var title: String = "" {
7
+ didSet {
8
+ updateUiAction()
9
+ if isMenuAction {
10
+ updateMenu()
11
+ }
12
+ }
13
+ }
14
+ var icon: String? {
15
+ didSet {
16
+ updateUiAction()
17
+ if isMenuAction {
18
+ updateMenu()
19
+ }
20
+ }
21
+ }
22
+ var destructive: Bool = false {
23
+ didSet {
24
+ updateUiAction()
25
+ if isMenuAction {
26
+ updateMenu()
27
+ }
28
+ }
29
+ }
30
+
31
+ // MARK: - Action only props
32
+ var disabled: Bool = false {
33
+ didSet {
34
+ updateUiAction()
35
+ }
36
+ }
37
+ var isOn: Bool = false {
38
+ didSet {
39
+ updateUiAction()
40
+ }
41
+ }
42
+ var keepPresented: Bool = false {
43
+ didSet {
44
+ updateUiAction()
45
+ }
46
+ }
47
+
48
+ // MARK: - Menu only props
49
+ var singleSelection: Bool = false {
50
+ didSet {
51
+ if isMenuAction {
52
+ updateMenu()
53
+ }
54
+ }
55
+ }
56
+ var displayAsPalette: Bool = false {
57
+ didSet {
58
+ if isMenuAction {
59
+ updateMenu()
60
+ }
61
+ }
62
+ }
63
+ var displayInline: Bool = false {
64
+ didSet {
65
+ if isMenuAction {
66
+ updateMenu()
67
+ }
68
+ }
69
+ }
70
+ var subActions: [LinkPreviewNativeActionView] = [] {
71
+ didSet {
72
+ updateMenu()
73
+ }
74
+ }
75
+
76
+ // MARK: - Events
77
+ let onSelected = EventDispatcher()
78
+
79
+ // MARK: - Native API
80
+ weak var parentMenuUpdatable: LinkPreviewMenuUpdatable?
81
+
82
+ private var baseUiAction: UIAction
83
+ private var menuAction: UIMenu
84
+
85
+ var isMenuAction: Bool {
86
+ return !subActions.isEmpty
87
+ }
88
+
89
+ var uiAction: UIMenuElement {
90
+ isMenuAction ? menuAction : baseUiAction
91
+ }
9
92
 
10
93
  required init(appContext: AppContext? = nil) {
94
+ baseUiAction = UIAction(title: "", handler: { _ in })
95
+ menuAction = UIMenu(title: "", image: nil, options: [], children: [])
11
96
  super.init(appContext: appContext)
12
97
  clipsToBounds = true
98
+ baseUiAction = UIAction(title: "", handler: { _ in self.onSelected() })
13
99
  }
14
100
 
15
- override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
16
- if let childActionView = childComponentView as? LinkPreviewNativeActionView {
17
- subActions.append(childActionView)
18
- } else {
19
- print(
20
- "ExpoRouter: Unknown child component view (\(childComponentView)) mounted to NativeLinkPreviewActionView"
21
- )
101
+ func updateMenu() {
102
+ let subActions = subActions.map { subAction in
103
+ subAction.uiAction
104
+ }
105
+ var options: UIMenu.Options = []
106
+ if #available(iOS 17.0, *) {
107
+ if displayAsPalette {
108
+ options.insert(.displayAsPalette)
109
+ }
110
+ }
111
+ if singleSelection {
112
+ options.insert(.singleSelection)
113
+ }
114
+ if displayInline {
115
+ options.insert(.displayInline)
116
+ }
117
+ if destructive {
118
+ options.insert(.destructive)
22
119
  }
120
+
121
+ menuAction = UIMenu(
122
+ title: title,
123
+ image: icon.flatMap { UIImage(systemName: $0) },
124
+ options: options,
125
+ children: subActions
126
+ )
127
+
128
+ parentMenuUpdatable?.updateMenu()
23
129
  }
24
130
 
25
- override func unmountChildComponentView(_ child: UIView, index: Int) {
26
- if let childActionView = child as? LinkPreviewNativeActionView {
27
- subActions.removeAll(where: { $0 == childActionView })
28
- } else {
29
- print(
30
- "ExpoRouter: Unknown child component view (\(child)) unmounted from NativeLinkPreviewActionView"
31
- )
131
+ private func updateUiAction() {
132
+ var attributes: UIMenuElement.Attributes = []
133
+ if destructive { attributes.insert(.destructive) }
134
+ if disabled { attributes.insert(.disabled) }
135
+
136
+ if #available(iOS 16.0, *) {
137
+ if keepPresented { attributes.insert(.keepsMenuPresented) }
32
138
  }
139
+
140
+ baseUiAction.title = title
141
+ baseUiAction.image = icon.flatMap { UIImage(systemName: $0) }
142
+ baseUiAction.attributes = attributes
143
+ baseUiAction.state = isOn ? .on : .off
144
+
145
+ parentMenuUpdatable?.updateMenu()
33
146
  }
147
+
148
+ #if RCT_NEW_ARCH_ENABLED
149
+ override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
150
+ if let childActionView = childComponentView as? LinkPreviewNativeActionView {
151
+ subActions.append(childActionView)
152
+ childActionView.parentMenuUpdatable = self
153
+ } else {
154
+ print(
155
+ "ExpoRouter: Unknown child component view (\(childComponentView)) mounted to NativeLinkPreviewActionView"
156
+ )
157
+ }
158
+ }
159
+
160
+ override func unmountChildComponentView(_ child: UIView, index: Int) {
161
+ if let childActionView = child as? LinkPreviewNativeActionView {
162
+ subActions.removeAll(where: { $0 == childActionView })
163
+ } else {
164
+ print(
165
+ "ExpoRouter: Unknown child component view (\(child)) unmounted from NativeLinkPreviewActionView"
166
+ )
167
+ }
168
+ }
169
+ #endif
170
+ }
171
+
172
+ protocol LinkPreviewMenuUpdatable: AnyObject {
173
+ func updateMenu()
34
174
  }
@@ -6,7 +6,11 @@ public class LinkPreviewNativeModule: Module {
6
6
 
7
7
  View(NativeLinkPreviewView.self) {
8
8
  Prop("nextScreenId") { (view: NativeLinkPreviewView, nextScreenId: String) in
9
- view.setNextScreenId(nextScreenId)
9
+ view.nextScreenId = nextScreenId
10
+ }
11
+
12
+ Prop("tabPath") { (view: NativeLinkPreviewView, tabPath: TabPathPayload) in
13
+ view.tabPath = tabPath
10
14
  }
11
15
 
12
16
  Events(
@@ -16,7 +20,6 @@ public class LinkPreviewNativeModule: Module {
16
20
  "onDidPreviewOpen",
17
21
  "onPreviewWillClose",
18
22
  "onPreviewDidClose",
19
- "onActionSelected"
20
23
  )
21
24
  }
22
25
 
@@ -38,17 +41,46 @@ public class LinkPreviewNativeModule: Module {
38
41
  }
39
42
 
40
43
  View(LinkPreviewNativeActionView.self) {
41
- Prop("id") { (view: LinkPreviewNativeActionView, id: String) in
42
- view.id = id
43
- }
44
44
  Prop("title") { (view: LinkPreviewNativeActionView, title: String) in
45
45
  view.title = title
46
46
  }
47
47
  Prop("icon") { (view: LinkPreviewNativeActionView, icon: String) in
48
48
  view.icon = icon
49
49
  }
50
+ Prop("disabled") { (view: LinkPreviewNativeActionView, disabled: Bool) in
51
+ view.disabled = disabled
52
+ }
53
+ Prop("destructive") { (view: LinkPreviewNativeActionView, destructive: Bool) in
54
+ view.destructive = destructive
55
+ }
56
+ Prop("singleSelection") { (view: LinkPreviewNativeActionView, singleSelection: Bool) in
57
+ view.singleSelection = singleSelection
58
+ }
59
+ Prop("displayAsPalette") { (view: LinkPreviewNativeActionView, displayAsPalette: Bool) in
60
+ view.displayAsPalette = displayAsPalette
61
+ }
62
+ Prop("isOn") { (view: LinkPreviewNativeActionView, isOn: Bool) in
63
+ view.isOn = isOn
64
+ }
65
+ Prop("keepPresented") { (view: LinkPreviewNativeActionView, keepPresented: Bool) in
66
+ view.keepPresented = keepPresented
67
+ }
68
+ Prop("displayInline") { (view: LinkPreviewNativeActionView, displayInline: Bool) in
69
+ view.displayInline = displayInline
70
+ }
71
+
72
+ Events("onSelected")
50
73
  }
51
74
 
52
75
  View(NativeLinkPreviewTrigger.self) {}
53
76
  }
54
77
  }
78
+
79
+ struct TabPathPayload: Record {
80
+ @Field var path: [TabStatePath]
81
+ }
82
+
83
+ struct TabStatePath: Record {
84
+ @Field var oldTabKey: String
85
+ @Field var newTabKey: String
86
+ }
@@ -1,24 +1,50 @@
1
1
  // Copyright 2015-present 650 Industries. All rights reserved.
2
2
 
3
- #import <RNScreens/RNSScreenStack.h>
3
+ #import <RNScreens/RNSDismissibleModalProtocol.h>
4
+ #import <RNScreens/RNSTabBarController.h>
4
5
 
5
- @interface LinkPreviewNativeNavigation: NSObject
6
+ @interface LinkPreviewNativeNavigationObjC : NSObject
6
7
 
7
8
  /*
8
- * Updates the preloaded view with the given screenId and UIResponder.
9
- * This function will go through the responder's view hierarchy to find the screen view with the given screenId and activity state 0.
10
- */
11
- - (void)updatePreloadedView:(NSString *)screenId withUiResponder:(UIResponder *)responder;
9
+ * Pushes the previously preloaded view.
10
+ * This function will set the activity state of the preloaded screen view to 2
11
+ */
12
+ + (void)pushPreloadedView:(nonnull UIView *)view
13
+ ontoStackView:(nonnull UIView *)rawStackView;
12
14
 
13
15
  /*
14
- * Pushes the previously preloaded view.
15
- * This function will set the activity state of the preloaded screen view to 2
16
- */
17
- - (void)pushPreloadedView;
16
+ * Helper function to check if the view is a RNSScreenStackView. Can be used in
17
+ * Swift
18
+ */
19
+ + (BOOL)isRNSScreenStackView:(UIView *)view;
20
+ /*
21
+ * Helper function to get all screen IDs from a RNSScreenStackView.
22
+ */
23
+ + (nonnull NSArray<NSString *> *)getStackViewScreenIds:(UIView *)view;
24
+ /*
25
+ * Helper function to get all screen views from a RNSScreenStackView.
26
+ */
27
+ + (nonnull NSArray<UIView *> *)getScreenViews:(UIView *)view;
28
+ /*
29
+ * Helper function to get the screen ID of a RNSScreenView.
30
+ */
31
+ + (nonnull NSString *)getScreenId:(UIView *)view;
32
+
33
+ + (nonnull NSString *)getTabKey:(UIView *)view;
34
+
35
+ + (BOOL)isRNSBottomTabsScreenComponentView:(UIView *)view;
36
+
37
+ + (BOOL)isRNSTabBarController:(UIView *)view;
38
+
39
+ + (nullable RNSTabBarController *)getBottomTabControllerFromView:(UIView *)view;
40
+
41
+ + (BOOL)isRNSBottomTabsHostComponentView:(UIView *)view;
42
+
43
+ + (nullable UIView *)getTab:(UITabBarController *)controller
44
+ withKey:(NSString *)key;
18
45
 
19
46
  @end
20
47
 
21
48
  @protocol LinkPreviewModalDismissible <RNSDismissibleModalProtocol>
22
49
  @required
23
50
  @end
24
-
@@ -6,12 +6,116 @@
6
6
  #import <RNScreens/RNSScreen.h>
7
7
  #import <RNScreens/RNSScreenStack.h>
8
8
 
9
- @implementation LinkPreviewNativeNavigation {
10
- RNSScreenView *preloadedScreenView;
11
- RNSScreenStackView *stackView;
9
+ @implementation LinkPreviewNativeNavigationObjC {
12
10
  }
13
11
 
14
- - (void)pushPreloadedView {
12
+ + (BOOL)isRNSScreenStackView:(UIView *)view {
13
+ if (view != nil) {
14
+ return [view isKindOfClass:[RNSScreenStackView class]];
15
+ }
16
+
17
+ return NO;
18
+ }
19
+
20
+ + (BOOL)isRNSBottomTabsScreenComponentView:(UIView *)view {
21
+ if (view != nil) {
22
+ return [view isKindOfClass:[RNSBottomTabsScreenComponentView class]];
23
+ }
24
+
25
+ return NO;
26
+ }
27
+
28
+ + (BOOL)isRNSBottomTabsHostComponentView:(UIView *)view {
29
+ if (view != nil) {
30
+ return [view isKindOfClass:[RNSBottomTabsHostComponentView class]];
31
+ }
32
+
33
+ return NO;
34
+ }
35
+
36
+ + (nullable UITabBarController *)getBottomTabControllerFromView:(UIView *)view {
37
+ if ([view isKindOfClass:[RNSBottomTabsScreenComponentView class]]) {
38
+ RNSBottomTabsScreenComponentView *bottomTabsView =
39
+ (RNSBottomTabsScreenComponentView *)view;
40
+ UIViewController *reactVC = [bottomTabsView reactViewController];
41
+ if (reactVC != nil &&
42
+ [reactVC.tabBarController isKindOfClass:[RNSTabBarController class]]) {
43
+ RNSTabBarController *tabBarController =
44
+ (RNSTabBarController *)reactVC.tabBarController;
45
+ return tabBarController;
46
+ }
47
+ }
48
+ if ([view isKindOfClass:[RNSBottomTabsHostComponentView class]]) {
49
+ RNSBottomTabsHostComponentView *bottomTabsView =
50
+ (RNSBottomTabsHostComponentView *)view;
51
+ return [bottomTabsView controller];
52
+ }
53
+ return nil;
54
+ }
55
+
56
+ + (nullable UIView *)getTab:(UITabBarController *)controller
57
+ withKey:(NSString *)key {
58
+ if (controller != nil) {
59
+ for (UIViewController *subcontroller in controller.viewControllers) {
60
+ if ([subcontroller.view
61
+ isKindOfClass:[RNSBottomTabsScreenComponentView class]]) {
62
+ if (((RNSBottomTabsScreenComponentView *)subcontroller.view).tabKey ==
63
+ key) {
64
+ return subcontroller.view;
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ return nil;
71
+ }
72
+
73
+ + (nonnull NSArray<NSString *> *)getStackViewScreenIds:(UIView *)view {
74
+ if (view != nil && [view isKindOfClass:[RNSScreenStackView class]]) {
75
+ RNSScreenStackView *stackView = (RNSScreenStackView *)view;
76
+ return stackView.screenIds;
77
+ }
78
+ return @[];
79
+ }
80
+
81
+ + (nonnull NSArray<UIView *> *)getScreenViews:(UIView *)view {
82
+ if (view != nil && [view isKindOfClass:[RNSScreenStackView class]]) {
83
+ RNSScreenStackView *stackView = (RNSScreenStackView *)view;
84
+ return stackView.reactSubviews;
85
+ }
86
+ return @[];
87
+ }
88
+
89
+ + (nonnull NSString *)getScreenId:(UIView *)view {
90
+ if (view != nil && [view isKindOfClass:[RNSScreenView class]]) {
91
+ RNSScreenView *screenView = (RNSScreenView *)view;
92
+ return screenView.screenId;
93
+ }
94
+ return nil;
95
+ }
96
+
97
+ + (nonnull NSString *)getTabKey:(UIView *)view {
98
+ if (view != nil &&
99
+ [view isKindOfClass:[RNSBottomTabsScreenComponentView class]]) {
100
+ RNSBottomTabsScreenComponentView *tabScreenView =
101
+ (RNSBottomTabsScreenComponentView *)view;
102
+ return tabScreenView.tabKey;
103
+ }
104
+ return @"";
105
+ }
106
+
107
+ + (void)pushPreloadedView:(nonnull UIView *)view
108
+ ontoStackView:(nonnull UIView *)rawStackView {
109
+ if (![LinkPreviewNativeNavigationObjC isRNSScreenStackView:rawStackView]) {
110
+ NSLog(@"ExpoRouter: The provided stack view is not a RNSScreenStackView.");
111
+ return;
112
+ }
113
+ if (![view isKindOfClass:[RNSScreenView class]]) {
114
+ NSLog(@"ExpoRouter: The provided view is not a RNSScreenView.");
115
+ return;
116
+ }
117
+ RNSScreenStackView *stackView = (RNSScreenStackView *)rawStackView;
118
+ RNSScreenView *preloadedScreenView = (RNSScreenView *)view;
15
119
  if (preloadedScreenView != nil && stackView != nil) {
16
120
  // Instead of pushing the preloaded screen view, we set its activity state
17
121
  // React native screens will then handle the rest.
@@ -41,7 +145,8 @@
41
145
  [firstChild isKindOfClass:[RNSScreenView class]]) {
42
146
  RNSScreenView *screenContentView = (RNSScreenView *)firstChild;
43
147
  // Same as above, we let React Native Screens handle the transition.
44
- // We need to set the activity of inner screen as well, because its react value is the same as the preloaded screen - 0.
148
+ // We need to set the activity of inner screen as well, because its
149
+ // react value is the same as the preloaded screen - 0.
45
150
  // https://github.com/software-mansion/react-native-screens/blob/8b82e081e8fdfa6e0864821134bda9e87a745b00/src/components/ScreenStackItem.tsx#L151
46
151
  [screenContentView setActivityState:2];
47
152
  [innerScreenStack markChildUpdated];
@@ -56,86 +161,4 @@
56
161
  }
57
162
  }
58
163
 
59
- - (void)updatePreloadedView:(nullable NSString *)screenId
60
- withUiResponder:(nonnull UIResponder *)responder {
61
- if (screenId != nil && [screenId length] > 0) {
62
- if ([self setPreloadedScreenViewWithScreenId:screenId
63
- withUiResponder:responder]) {
64
- NSLog(@"ExpoRouter: Preloaded screen view updated.");
65
- } else {
66
- NSLog(@"ExpoRouter: No native screen view found with screenId: %@",
67
- screenId);
68
- }
69
- } else {
70
- preloadedScreenView = nil;
71
- }
72
- }
73
-
74
- - (nonnull NSArray<RNSScreenStackView *> *)
75
- findAllScreenStackViewsInResponderChain:(nonnull UIResponder *)responder {
76
- NSMutableArray<RNSScreenStackView *> *stackViews = [NSMutableArray array];
77
-
78
- while (responder) {
79
- responder = [responder nextResponder];
80
- if ([responder isKindOfClass:[RNSScreenStackView class]]) {
81
- [stackViews addObject:(RNSScreenStackView *)responder];
82
- }
83
- }
84
-
85
- return stackViews;
86
- }
87
-
88
- - (nonnull NSArray<RNSScreenView *> *)extractScreenViewsFromSubviews:
89
- (nonnull NSArray<UIView *> *)subviews {
90
- NSMutableArray<RNSScreenView *> *screenViews = [NSMutableArray array];
91
-
92
- for (UIView *subview in subviews) {
93
- if ([subview isKindOfClass:[RNSScreenView class]]) {
94
- [screenViews addObject:(RNSScreenView *)subview];
95
- }
96
- }
97
- return screenViews;
98
- }
99
-
100
- - (BOOL)setPreloadedScreenViewWithScreenId:(nonnull NSString *)screenId
101
- withUiResponder:(nonnull UIResponder *)responder {
102
- NSArray<RNSScreenStackView *> *stacks =
103
- [self findAllScreenStackViewsInResponderChain:responder];
104
-
105
- for (RNSScreenStackView *stack in stacks) {
106
- if ([stack.screenIds containsObject:screenId] &&
107
- [self setPreloadedScreenViewWithScreenId:screenId
108
- withStackView:stack]) {
109
- return YES;
110
- }
111
- }
112
- return NO;
113
- }
114
-
115
- - (BOOL)setPreloadedScreenViewWithScreenId:(nonnull NSString *)screenId
116
- withStackView:(nonnull RNSScreenStackView *)stack {
117
- NSArray<RNSScreenView *> *screenSubviews =
118
- [self extractScreenViewsFromSubviews:stack.reactSubviews];
119
- RNSScreenView *screenView = [self findPreloadedScreenView:screenSubviews
120
- withScreenId:screenId];
121
- if (screenView != nil) {
122
- preloadedScreenView = screenView;
123
- stackView = stack;
124
- return YES;
125
- }
126
- return NO;
127
- }
128
-
129
- - (nullable RNSScreenView *)
130
- findPreloadedScreenView:(nonnull NSArray<RNSScreenView *> *)screenViews
131
- withScreenId:(nonnull NSString *)screenId {
132
- for (RNSScreenView *screenView in screenViews) {
133
- if (screenView.activityState == 0 &&
134
- [screenView.screenId isEqualToString:screenId]) {
135
- return screenView;
136
- }
137
- }
138
- return nil;
139
- }
140
-
141
164
  @end
@@ -0,0 +1,136 @@
1
+ import UIKit
2
+
3
+ struct TabChangeCommand {
4
+ weak var tabBarController: UITabBarController?
5
+ let tabIndex: Int
6
+ }
7
+
8
+ internal class LinkPreviewNativeNavigation {
9
+ private var preloadedView: UIView?
10
+ private var preloadedStackView: UIView?
11
+ private var tabChangeCommands: [TabChangeCommand] = []
12
+
13
+ init() {}
14
+
15
+ func pushPreloadedView() {
16
+ self.tabChangeCommands.forEach { command in
17
+ command.tabBarController?.selectedIndex = command.tabIndex
18
+ }
19
+ guard let preloadedView,
20
+ let preloadedStackView
21
+ else {
22
+ return
23
+ }
24
+ LinkPreviewNativeNavigationObjC.pushPreloadedView(
25
+ preloadedView, ontoStackView: preloadedStackView)
26
+ }
27
+
28
+ func updatePreloadedView(screenId: String?, tabPath: TabPathPayload?, responder: UIView) {
29
+ self.tabChangeCommands = []
30
+ let oldTabKeys = tabPath?.path.map { $0.oldTabKey } ?? []
31
+ let stackOrTabView = findStackViewWithScreenIdOrTabBarController(
32
+ screenId: screenId, tabKeys: oldTabKeys, responder: responder)
33
+ if let stackOrTabView = stackOrTabView {
34
+ if LinkPreviewNativeNavigationObjC.isRNSBottomTabsScreenComponentView(stackOrTabView) {
35
+ let tabView = stackOrTabView
36
+ let newTabKeys = tabPath?.path.map { $0.newTabKey } ?? []
37
+ let stackView = findStackViewWithScreenIdInSubViews(
38
+ screenId: screenId, tabKeys: newTabKeys, rootView: tabView)
39
+ if let stackView = stackView {
40
+ let screenViews = LinkPreviewNativeNavigationObjC.getScreenViews(stackView)
41
+ if let screenView = screenViews.first(where: {
42
+ LinkPreviewNativeNavigationObjC.getScreenId($0) == screenId
43
+ }) {
44
+ preloadedView = screenView
45
+ preloadedStackView = stackView
46
+ print("LinkPreviewNativeNavigation: Preloaded view for screenId \(screenId).")
47
+ }
48
+ }
49
+ } else if LinkPreviewNativeNavigationObjC.isRNSScreenStackView(stackOrTabView) {
50
+ let stackView = stackOrTabView
51
+ let screenViews = LinkPreviewNativeNavigationObjC.getScreenViews(stackView)
52
+ if let screenView = screenViews.first(where: {
53
+ LinkPreviewNativeNavigationObjC.getScreenId($0) == screenId
54
+ }) {
55
+ preloadedView = screenView
56
+ preloadedStackView = stackView
57
+ print("LinkPreviewNativeNavigation: Preloaded view for screenId \(screenId).")
58
+ }
59
+ }
60
+ } else {
61
+ print("LinkPreviewNativeNavigation: No stack view found for screenId \(screenId).")
62
+ }
63
+ }
64
+
65
+ private func findStackViewWithScreenIdInSubViews(
66
+ screenId: String?, tabKeys: [String], rootView: UIView
67
+ ) -> UIView? {
68
+ if LinkPreviewNativeNavigationObjC.isRNSScreenStackView(rootView),
69
+ let _screenId = screenId {
70
+ let screenIds = LinkPreviewNativeNavigationObjC.getStackViewScreenIds(rootView)
71
+ if screenIds.contains(_screenId) {
72
+ return rootView
73
+ }
74
+ } else if LinkPreviewNativeNavigationObjC.isRNSBottomTabsScreenComponentView(rootView)
75
+ || LinkPreviewNativeNavigationObjC.isRNSBottomTabsHostComponentView(rootView) {
76
+ let tabBarController = LinkPreviewNativeNavigationObjC.getBottomTabController(from: rootView)
77
+ if let tabBarController = tabBarController {
78
+ let views = tabBarController.viewControllers?.compactMap { $0.view } ?? []
79
+ let enumeratedViews = views.enumerated()
80
+ if let (tabIndex, tabView) =
81
+ enumeratedViews
82
+ .first(where: { _, view in
83
+ LinkPreviewNativeNavigationObjC.isRNSBottomTabsScreenComponentView(view)
84
+ && tabKeys.contains(LinkPreviewNativeNavigationObjC.getTabKey(view))
85
+ }) {
86
+ self.tabChangeCommands.append(
87
+ TabChangeCommand(tabBarController: tabBarController, tabIndex: tabIndex))
88
+ let test = tabBarController.viewControllers
89
+
90
+ for subview in tabView.subviews {
91
+ let result = findStackViewWithScreenIdInSubViews(
92
+ screenId: screenId, tabKeys: tabKeys, rootView: subview)
93
+ if result != nil {
94
+ return result
95
+ }
96
+ }
97
+ }
98
+ }
99
+ } else {
100
+ for subview in rootView.subviews {
101
+ let result = findStackViewWithScreenIdInSubViews(
102
+ screenId: screenId, tabKeys: tabKeys, rootView: subview)
103
+ if result != nil {
104
+ return result
105
+ }
106
+ }
107
+ }
108
+
109
+ return nil
110
+ }
111
+
112
+ private func findStackViewWithScreenIdOrTabBarController(
113
+ screenId: String?, tabKeys: [String], responder: UIView
114
+ ) -> UIView? {
115
+ var currentResponder: UIResponder? = responder
116
+
117
+ while let nextResponder = currentResponder?.next {
118
+ if let view = nextResponder as? UIView,
119
+ LinkPreviewNativeNavigationObjC.isRNSScreenStackView(view),
120
+ let _screenId = screenId {
121
+ let screenIds = LinkPreviewNativeNavigationObjC.getStackViewScreenIds(view)
122
+ if screenIds.contains(_screenId) {
123
+ return view
124
+ }
125
+ } else if let tabView = nextResponder as? UIView,
126
+ LinkPreviewNativeNavigationObjC.isRNSBottomTabsScreenComponentView(tabView) {
127
+ let tabKey = LinkPreviewNativeNavigationObjC.getTabKey(tabView)
128
+ if tabKeys.contains(tabKey) {
129
+ return tabView
130
+ }
131
+ }
132
+ currentResponder = nextResponder
133
+ }
134
+ return nil
135
+ }
136
+ }
@@ -10,6 +10,8 @@ class NativeLinkPreviewContentView: ExpoView {
10
10
  }
11
11
 
12
12
  func setInitialSize(bounds: CGRect) {
13
+ #if RCT_NEW_ARCH_ENABLED
13
14
  self.setShadowNodeSize(Float(bounds.width), height: Float(bounds.height))
15
+ #endif
14
16
  }
15
17
  }