expo-router 7.0.0-canary-20251127-587bc53 → 7.0.0-canary-20251205-756eb7a
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/android/build.gradle +2 -2
- package/build/fork/getStateFromPath-forks.d.ts +5 -10
- package/build/fork/getStateFromPath-forks.d.ts.map +1 -1
- package/build/fork/getStateFromPath-forks.js +14 -13
- package/build/fork/getStateFromPath-forks.js.map +1 -1
- package/build/fork/getStateFromPath.js +3 -3
- package/build/fork/getStateFromPath.js.map +1 -1
- package/build/fork/native-stack/createNativeStackNavigator.d.ts.map +1 -1
- package/build/fork/native-stack/createNativeStackNavigator.js +12 -3
- package/build/fork/native-stack/createNativeStackNavigator.js.map +1 -1
- package/build/fork/native-stack/descriptors-context.d.ts +8 -0
- package/build/fork/native-stack/descriptors-context.d.ts.map +1 -0
- package/build/fork/native-stack/descriptors-context.js +6 -0
- package/build/fork/native-stack/descriptors-context.js.map +1 -0
- package/build/internal/utils.d.ts +1 -0
- package/build/internal/utils.d.ts.map +1 -1
- package/build/internal/utils.js +3 -1
- package/build/internal/utils.js.map +1 -1
- package/build/layouts/StackClient.d.ts.map +1 -1
- package/build/layouts/StackClient.js +34 -1
- package/build/layouts/StackClient.js.map +1 -1
- package/build/link/ExpoLink.d.ts.map +1 -1
- package/build/link/ExpoLink.js +12 -4
- package/build/link/ExpoLink.js.map +1 -1
- package/build/link/Link.d.ts +2 -0
- package/build/link/Link.d.ts.map +1 -1
- package/build/link/Link.js +2 -0
- package/build/link/Link.js.map +1 -1
- package/build/link/LinkWithPreview.d.ts +6 -1
- package/build/link/LinkWithPreview.d.ts.map +1 -1
- package/build/link/LinkWithPreview.js +6 -5
- package/build/link/LinkWithPreview.js.map +1 -1
- package/build/link/elements.d.ts +8 -0
- package/build/link/elements.d.ts.map +1 -1
- package/build/link/elements.js +7 -2
- package/build/link/elements.js.map +1 -1
- package/build/link/preview/native.d.ts +17 -0
- package/build/link/preview/native.d.ts.map +1 -1
- package/build/link/preview/native.js +22 -0
- package/build/link/preview/native.js.map +1 -1
- package/build/link/zoom/ZoomTransitionEnabler.d.ts +5 -0
- package/build/link/zoom/ZoomTransitionEnabler.d.ts.map +1 -0
- package/build/link/zoom/ZoomTransitionEnabler.ios.d.ts +5 -0
- package/build/link/zoom/ZoomTransitionEnabler.ios.d.ts.map +1 -0
- package/build/link/zoom/ZoomTransitionEnabler.ios.js +44 -0
- package/build/link/zoom/ZoomTransitionEnabler.ios.js.map +1 -0
- package/build/link/zoom/ZoomTransitionEnabler.js +13 -0
- package/build/link/zoom/ZoomTransitionEnabler.js.map +1 -0
- package/build/link/zoom/ZoomTransitionEnabler.types.d.ts +4 -0
- package/build/link/zoom/ZoomTransitionEnabler.types.d.ts.map +1 -0
- package/build/link/zoom/ZoomTransitionEnabler.types.js +3 -0
- package/build/link/zoom/ZoomTransitionEnabler.types.js.map +1 -0
- package/build/link/zoom/link-apple-zoom.d.ts +23 -0
- package/build/link/zoom/link-apple-zoom.d.ts.map +1 -0
- package/build/link/zoom/link-apple-zoom.js +40 -0
- package/build/link/zoom/link-apple-zoom.js.map +1 -0
- package/build/link/zoom/useZoomTransitionPrimitives.d.ts +6 -0
- package/build/link/zoom/useZoomTransitionPrimitives.d.ts.map +1 -0
- package/build/link/zoom/useZoomTransitionPrimitives.ios.d.ts +11 -0
- package/build/link/zoom/useZoomTransitionPrimitives.ios.d.ts.map +1 -0
- package/build/link/zoom/useZoomTransitionPrimitives.ios.js +66 -0
- package/build/link/zoom/useZoomTransitionPrimitives.ios.js.map +1 -0
- package/build/link/zoom/useZoomTransitionPrimitives.js +9 -0
- package/build/link/zoom/useZoomTransitionPrimitives.js.map +1 -0
- package/build/link/zoom/zoom-transition-context.d.ts +7 -0
- package/build/link/zoom/zoom-transition-context.d.ts.map +1 -0
- package/build/link/zoom/zoom-transition-context.js +6 -0
- package/build/link/zoom/zoom-transition-context.js.map +1 -0
- package/build/native-tabs/NativeBottomTabsNavigator.d.ts +3 -15
- package/build/native-tabs/NativeBottomTabsNavigator.d.ts.map +1 -1
- package/build/native-tabs/NativeBottomTabsNavigator.js +10 -2
- package/build/native-tabs/NativeBottomTabsNavigator.js.map +1 -1
- package/build/native-tabs/NativeBottomTabsRouter.d.ts.map +1 -1
- package/build/native-tabs/NativeBottomTabsRouter.js +12 -1
- package/build/native-tabs/NativeBottomTabsRouter.js.map +1 -1
- package/build/native-tabs/NativeTabTrigger.d.ts +1 -1
- package/build/native-tabs/NativeTabTrigger.d.ts.map +1 -1
- package/build/native-tabs/NativeTabTrigger.js +3 -1
- package/build/native-tabs/NativeTabTrigger.js.map +1 -1
- package/build/native-tabs/NativeTabs.d.ts +6 -3
- package/build/native-tabs/NativeTabs.d.ts.map +1 -1
- package/build/native-tabs/NativeTabs.js +7 -2
- package/build/native-tabs/NativeTabs.js.map +1 -1
- package/build/native-tabs/NativeTabsView.d.ts.map +1 -1
- package/build/native-tabs/NativeTabsView.js +26 -3
- package/build/native-tabs/NativeTabsView.js.map +1 -1
- package/build/native-tabs/common/elements.d.ts +25 -0
- package/build/native-tabs/common/elements.d.ts.map +1 -1
- package/build/native-tabs/common/elements.js +26 -1
- package/build/native-tabs/common/elements.js.map +1 -1
- package/build/native-tabs/hooks.d.ts +45 -0
- package/build/native-tabs/hooks.d.ts.map +1 -0
- package/build/native-tabs/hooks.js +57 -0
- package/build/native-tabs/hooks.js.map +1 -0
- package/build/native-tabs/types.d.ts +36 -2
- package/build/native-tabs/types.d.ts.map +1 -1
- package/build/native-tabs/types.js.map +1 -1
- package/build/native-tabs/utils/bottomAccessory.d.ts +9 -0
- package/build/native-tabs/utils/bottomAccessory.d.ts.map +1 -0
- package/build/native-tabs/utils/bottomAccessory.js +17 -0
- package/build/native-tabs/utils/bottomAccessory.js.map +1 -0
- package/build/navigationParams.d.ts +3 -1
- package/build/navigationParams.d.ts.map +1 -1
- package/build/navigationParams.js +5 -1
- package/build/navigationParams.js.map +1 -1
- package/build/toolbar/elements.d.ts +24 -0
- package/build/toolbar/elements.d.ts.map +1 -0
- package/build/toolbar/elements.js +36 -0
- package/build/toolbar/elements.js.map +1 -0
- package/build/toolbar/index.d.ts +9 -0
- package/build/toolbar/index.d.ts.map +1 -0
- package/build/toolbar/index.js +12 -0
- package/build/toolbar/index.js.map +1 -0
- package/build/toolbar/native.d.ts +4 -0
- package/build/toolbar/native.d.ts.map +1 -0
- package/build/toolbar/native.ios.d.ts +4 -0
- package/build/toolbar/native.ios.d.ts.map +1 -0
- package/build/toolbar/native.ios.js +21 -0
- package/build/toolbar/native.ios.js.map +1 -0
- package/build/toolbar/native.js +11 -0
- package/build/toolbar/native.js.map +1 -0
- package/build/toolbar/native.types.d.ts +30 -0
- package/build/toolbar/native.types.d.ts.map +1 -0
- package/build/toolbar/native.types.js +3 -0
- package/build/toolbar/native.types.js.map +1 -0
- package/build/useScreens.d.ts.map +1 -1
- package/build/useScreens.js +13 -5
- package/build/useScreens.js.map +1 -1
- package/build/views/Sitemap.js +3 -2
- package/build/views/Sitemap.js.map +1 -1
- package/expo-module.config.json +2 -2
- package/ios/LinkPreview/LinkPreviewNativeActionView.swift +1 -0
- package/ios/LinkPreview/LinkPreviewNativeModule.swift +49 -1
- package/ios/LinkPreview/LinkPreviewNativeNavigation.swift +132 -70
- package/ios/LinkPreview/LinkPreviewNativeView.swift +28 -14
- package/ios/LinkPreview/LinkZoomTransition.swift +227 -0
- package/ios/Toolbar/RouterToolbarHostView.swift +140 -0
- package/ios/Toolbar/RouterToolbarItemView.swift +171 -0
- package/ios/Toolbar/RouterToolbarModule.swift +102 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.module → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.module} +7 -7
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.module.md5 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.pom → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.pom} +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha512 +1 -1
- package/package.json +14 -12
- package/unstable-toolbar.d.ts +1 -0
- package/unstable-toolbar.js +1 -0
- package/ios/LinkPreview/LinkPreviewNativeNavigation.h +0 -50
- package/ios/LinkPreview/LinkPreviewNativeNavigation.mm +0 -164
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.module.md5 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.pom.sha512 +0 -1
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53-sources.jar → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a-sources.jar} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53-sources.jar.md5 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a-sources.jar.md5} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53-sources.jar.sha1 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a-sources.jar.sha1} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53-sources.jar.sha256 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a-sources.jar.sha256} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53-sources.jar.sha512 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a-sources.jar.sha512} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.aar → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.aar} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.aar.md5 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.aar.md5} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.aar.sha1 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.aar.sha1} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.aar.sha256 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.aar.sha256} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{7.0.0-canary-20251127-587bc53/expo.modules.router-7.0.0-canary-20251127-587bc53.aar.sha512 → 7.0.0-canary-20251205-756eb7a/expo.modules.router-7.0.0-canary-20251205-756eb7a.aar.sha512} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
|
+
import RNScreens
|
|
2
3
|
|
|
3
4
|
class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
|
|
4
|
-
|
|
5
|
-
{
|
|
5
|
+
RNSDismissibleModalProtocol, LinkPreviewMenuUpdatable {
|
|
6
6
|
private var preview: NativeLinkPreviewContentView?
|
|
7
7
|
private var interaction: UIContextMenuInteraction?
|
|
8
|
-
|
|
8
|
+
var directChild: UIView?
|
|
9
9
|
var nextScreenId: String? {
|
|
10
10
|
didSet {
|
|
11
11
|
performUpdateOfPreloadedView()
|
|
@@ -18,7 +18,9 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
|
|
|
18
18
|
}
|
|
19
19
|
private var actions: [LinkPreviewNativeActionView] = []
|
|
20
20
|
|
|
21
|
-
private
|
|
21
|
+
private lazy var linkPreviewNativeNavigation: LinkPreviewNativeNavigation = {
|
|
22
|
+
return LinkPreviewNativeNavigation(logger: appContext?.jsLogger)
|
|
23
|
+
}()
|
|
22
24
|
|
|
23
25
|
let onPreviewTapped = EventDispatcher()
|
|
24
26
|
let onPreviewTappedAnimationCompleted = EventDispatcher()
|
|
@@ -67,7 +69,11 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
|
|
|
67
69
|
}
|
|
68
70
|
directChild = childComponentView
|
|
69
71
|
if let interaction = self.interaction {
|
|
70
|
-
childComponentView
|
|
72
|
+
if let indirectTrigger = childComponentView as? LinkPreviewIndirectTriggerProtocol {
|
|
73
|
+
indirectTrigger.indirectTrigger?.addInteraction(interaction)
|
|
74
|
+
} else {
|
|
75
|
+
childComponentView.addInteraction(interaction)
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
78
|
super.mountChildComponentView(childComponentView, index: index)
|
|
73
79
|
}
|
|
@@ -89,7 +95,11 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
|
|
|
89
95
|
return
|
|
90
96
|
}
|
|
91
97
|
if let interaction = self.interaction {
|
|
92
|
-
directChild
|
|
98
|
+
if let indirectTrigger = directChild as? LinkPreviewIndirectTriggerProtocol {
|
|
99
|
+
indirectTrigger.indirectTrigger?.removeInteraction(interaction)
|
|
100
|
+
} else {
|
|
101
|
+
directChild.removeInteraction(interaction)
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
super.unmountChildComponentView(child, index: index)
|
|
95
105
|
} else {
|
|
@@ -124,16 +134,16 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate,
|
|
|
124
134
|
configuration: UIContextMenuConfiguration,
|
|
125
135
|
highlightPreviewForItemWithIdentifier identifier: any NSCopying
|
|
126
136
|
) -> UITargetedPreview? {
|
|
127
|
-
if let superview
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
if let superview, let directChild {
|
|
138
|
+
let triggerView: UIView =
|
|
139
|
+
(directChild as? LinkPreviewIndirectTriggerProtocol)?.indirectTrigger ?? directChild
|
|
140
|
+
let target = UIPreviewTarget(
|
|
141
|
+
container: superview, center: self.convert(triggerView.center, to: superview))
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
let parameters = UIPreviewParameters()
|
|
144
|
+
parameters.backgroundColor = .clear
|
|
134
145
|
|
|
135
|
-
|
|
136
|
-
}
|
|
146
|
+
return UITargetedPreview(view: triggerView, parameters: parameters, target: target)
|
|
137
147
|
}
|
|
138
148
|
return nil
|
|
139
149
|
}
|
|
@@ -232,3 +242,7 @@ class PreviewViewController: UIViewController {
|
|
|
232
242
|
linkPreviewNativePreview.setInitialSize(bounds: self.view.bounds)
|
|
233
243
|
}
|
|
234
244
|
}
|
|
245
|
+
|
|
246
|
+
protocol LinkPreviewIndirectTriggerProtocol {
|
|
247
|
+
var indirectTrigger: UIView? { get }
|
|
248
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import RNScreens
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
class LinkSourceInfo {
|
|
6
|
+
let alignment: CGRect?
|
|
7
|
+
weak var view: UIView?
|
|
8
|
+
|
|
9
|
+
init(alignment: CGRect?, view: UIView) {
|
|
10
|
+
self.alignment = alignment
|
|
11
|
+
self.view = view
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class LinkZoomTransitionsSourceRepository {
|
|
16
|
+
private var sources: [String: LinkSourceInfo] = [:]
|
|
17
|
+
private let lock = NSLock()
|
|
18
|
+
|
|
19
|
+
init() {}
|
|
20
|
+
|
|
21
|
+
func registerSource(
|
|
22
|
+
identifier: String,
|
|
23
|
+
source: LinkSourceInfo
|
|
24
|
+
) {
|
|
25
|
+
lock.lock()
|
|
26
|
+
defer { lock.unlock() }
|
|
27
|
+
if sources[identifier] != nil {
|
|
28
|
+
print(
|
|
29
|
+
"[expo-router] LinkPreviewZoomTransitionSource with identifier \(identifier) is already registered. Overwriting the existing source."
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
if !identifier.isEmpty {
|
|
33
|
+
sources[identifier] = source
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func unregisterSource(identifier: String) {
|
|
38
|
+
lock.lock()
|
|
39
|
+
defer { lock.unlock() }
|
|
40
|
+
sources.removeValue(forKey: identifier)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func getSource(identifier: String) -> LinkSourceInfo? {
|
|
44
|
+
lock.lock()
|
|
45
|
+
defer { lock.unlock() }
|
|
46
|
+
return sources[identifier]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func updateIdentifier(
|
|
50
|
+
oldIdentifier: String,
|
|
51
|
+
newIdentifier: String
|
|
52
|
+
) {
|
|
53
|
+
lock.lock()
|
|
54
|
+
defer { lock.unlock() }
|
|
55
|
+
if let source = sources[oldIdentifier] {
|
|
56
|
+
if !newIdentifier.isEmpty {
|
|
57
|
+
sources[newIdentifier] = source
|
|
58
|
+
}
|
|
59
|
+
sources.removeValue(forKey: oldIdentifier)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func updateAlignment(
|
|
64
|
+
identifier: String,
|
|
65
|
+
alignment: CGRect?
|
|
66
|
+
) {
|
|
67
|
+
lock.lock()
|
|
68
|
+
defer { lock.unlock() }
|
|
69
|
+
if let source = sources[identifier], let view = source.view, !identifier.isEmpty {
|
|
70
|
+
sources[identifier] = LinkSourceInfo(
|
|
71
|
+
alignment: alignment,
|
|
72
|
+
view: view
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class LinkZoomTransitionSource: LinkZoomExpoView, LinkPreviewIndirectTriggerProtocol {
|
|
79
|
+
var child: UIView?
|
|
80
|
+
|
|
81
|
+
var indirectTrigger: UIView? {
|
|
82
|
+
return child
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
var alignment: CGRect? {
|
|
86
|
+
didSet {
|
|
87
|
+
// Update alignment info in the repository
|
|
88
|
+
if child != nil {
|
|
89
|
+
repository?.updateAlignment(
|
|
90
|
+
identifier: identifier,
|
|
91
|
+
alignment: alignment
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
var identifier: String = "" {
|
|
98
|
+
didSet {
|
|
99
|
+
guard identifier != oldValue else { return }
|
|
100
|
+
if let child {
|
|
101
|
+
if oldValue.isEmpty {
|
|
102
|
+
repository?.registerSource(
|
|
103
|
+
identifier: identifier,
|
|
104
|
+
source: LinkSourceInfo(alignment: alignment, view: child)
|
|
105
|
+
)
|
|
106
|
+
} else {
|
|
107
|
+
repository?.updateIdentifier(
|
|
108
|
+
oldIdentifier: oldValue,
|
|
109
|
+
newIdentifier: identifier
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
repository?.unregisterSource(
|
|
114
|
+
identifier: oldValue
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override func mountChildComponentView(
|
|
121
|
+
_ childComponentView: UIView,
|
|
122
|
+
index: Int
|
|
123
|
+
) {
|
|
124
|
+
if child != nil {
|
|
125
|
+
print(
|
|
126
|
+
"[expo-router] LinkZoomTransitionSource can only have one child view."
|
|
127
|
+
)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
child = childComponentView
|
|
131
|
+
repository?.registerSource(
|
|
132
|
+
identifier: identifier,
|
|
133
|
+
source: LinkSourceInfo(alignment: alignment, view: childComponentView)
|
|
134
|
+
)
|
|
135
|
+
super.mountChildComponentView(childComponentView, index: index)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override func unmountChildComponentView(_ child: UIView, index: Int) {
|
|
139
|
+
if child == self.child {
|
|
140
|
+
self.child = nil
|
|
141
|
+
repository?.unregisterSource(
|
|
142
|
+
identifier: identifier
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
super.unmountChildComponentView(child, index: index)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class LinkZoomTransitionEnabler: LinkZoomExpoView {
|
|
150
|
+
var zoomTransitionSourceIdentifier: String = ""
|
|
151
|
+
var isPreventingInteractiveDismissal: Bool = false
|
|
152
|
+
|
|
153
|
+
override func didMoveToSuperview() {
|
|
154
|
+
super.didMoveToSuperview()
|
|
155
|
+
if superview != nil {
|
|
156
|
+
// Need to run this async. Otherwise the view has no view controller yet
|
|
157
|
+
DispatchQueue.main.async {
|
|
158
|
+
self.setupZoomTransition()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func setupZoomTransition() {
|
|
164
|
+
if self.zoomTransitionSourceIdentifier.isEmpty {
|
|
165
|
+
print("[expo-router] No zoomTransitionSourceIdentifier passed to LinkZoomTransitionEnabler")
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
if let controller = self.findViewController() {
|
|
169
|
+
if #available(iOS 18.0, *) {
|
|
170
|
+
let options = UIViewController.Transition.ZoomOptions()
|
|
171
|
+
|
|
172
|
+
options.alignmentRectProvider = { _ in
|
|
173
|
+
let sourceInfo = self.repository?.getSource(
|
|
174
|
+
identifier: self.zoomTransitionSourceIdentifier)
|
|
175
|
+
return sourceInfo?.alignment
|
|
176
|
+
}
|
|
177
|
+
options.interactiveDismissShouldBegin = { _ in
|
|
178
|
+
!self.isPreventingInteractiveDismissal
|
|
179
|
+
}
|
|
180
|
+
controller.preferredTransition = .zoom(options: options) { _ in
|
|
181
|
+
let sourceInfo = self.repository?.getSource(
|
|
182
|
+
identifier: self.zoomTransitionSourceIdentifier)
|
|
183
|
+
var view: UIView? = sourceInfo?.view
|
|
184
|
+
if let linkPreviewView = view as? NativeLinkPreviewView {
|
|
185
|
+
view = linkPreviewView.directChild
|
|
186
|
+
}
|
|
187
|
+
guard let view else {
|
|
188
|
+
print(
|
|
189
|
+
"[expo-router] No source view found for identifier \(self.zoomTransitionSourceIdentifier) to enable zoom transition"
|
|
190
|
+
)
|
|
191
|
+
return nil
|
|
192
|
+
}
|
|
193
|
+
return view
|
|
194
|
+
}
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
print("[expo-router] No navigation controller found to enable zoom transition")
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private func findViewController() -> RNSScreen? {
|
|
203
|
+
var responder: UIResponder? = self
|
|
204
|
+
while let r = responder {
|
|
205
|
+
if let r = r as? RNSScreen {
|
|
206
|
+
return r
|
|
207
|
+
}
|
|
208
|
+
responder = r.next
|
|
209
|
+
}
|
|
210
|
+
return nil
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class LinkZoomExpoView: ExpoView {
|
|
215
|
+
var module: LinkPreviewNativeModule? {
|
|
216
|
+
return appContext?.moduleRegistry.get(moduleWithName: LinkPreviewNativeModule.moduleName)
|
|
217
|
+
as? LinkPreviewNativeModule
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
var repository: LinkZoomTransitionsSourceRepository? {
|
|
221
|
+
guard let module else {
|
|
222
|
+
print("[expo-router] LinkPreviewNativeModule not loaded")
|
|
223
|
+
return nil
|
|
224
|
+
}
|
|
225
|
+
return module.zoomSourceRepository
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import RNScreens
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
class RouterToolbarHostView: ExpoView, LinkPreviewMenuUpdatable {
|
|
6
|
+
// Mutable map of toolbar items
|
|
7
|
+
var toolbarItemsArray: [String] = []
|
|
8
|
+
var toolbarItemsMap: [String: RouterToolbarItemView] = [:]
|
|
9
|
+
var menuItemsMap: [String: LinkPreviewNativeActionView] = [:]
|
|
10
|
+
|
|
11
|
+
required init(appContext: AppContext? = nil) {
|
|
12
|
+
super.init(appContext: appContext)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private func addRouterToolbarItemAtIndex(
|
|
16
|
+
_ item: RouterToolbarItemView,
|
|
17
|
+
index: Int
|
|
18
|
+
) {
|
|
19
|
+
let identifier = item.identifier
|
|
20
|
+
toolbarItemsArray.insert(identifier, at: index)
|
|
21
|
+
toolbarItemsMap[identifier] = item
|
|
22
|
+
item.host = self
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private func addMenuToolbarItemAtIndex(
|
|
26
|
+
_ item: LinkPreviewNativeActionView,
|
|
27
|
+
index: Int
|
|
28
|
+
) {
|
|
29
|
+
let identifier = item.identifier
|
|
30
|
+
toolbarItemsArray.insert(identifier, at: index)
|
|
31
|
+
menuItemsMap[identifier] = item
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private func removeToolbarItemWithId(_ id: String) {
|
|
35
|
+
if let index = toolbarItemsArray.firstIndex(of: id) {
|
|
36
|
+
toolbarItemsArray.remove(at: index)
|
|
37
|
+
toolbarItemsMap.removeValue(forKey: id)
|
|
38
|
+
menuItemsMap.removeValue(forKey: id)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func updateToolbarItem(withId id: String) {
|
|
43
|
+
if let controller = self.findViewController() {
|
|
44
|
+
let index = toolbarItemsArray.firstIndex(of: id)
|
|
45
|
+
if let index = index, let item = toolbarItemsMap[id] {
|
|
46
|
+
controller.toolbarItems?[index] = item.barButtonItem
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func updateToolbarItems() {
|
|
52
|
+
if let controller = self.findViewController() {
|
|
53
|
+
if #available(iOS 18.0, *) {
|
|
54
|
+
controller.setToolbarItems(
|
|
55
|
+
toolbarItemsArray.compactMap {
|
|
56
|
+
if let item = toolbarItemsMap[$0] {
|
|
57
|
+
return item.barButtonItem
|
|
58
|
+
}
|
|
59
|
+
if let menu = menuItemsMap[$0] {
|
|
60
|
+
return UIBarButtonItem(
|
|
61
|
+
title: menu.title,
|
|
62
|
+
image: menu.icon.flatMap { UIImage(systemName: $0) },
|
|
63
|
+
primaryAction: nil,
|
|
64
|
+
menu: menu.uiAction as? UIMenu
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
print(
|
|
68
|
+
"[expo-router] Warning: Could not find toolbar item or menu for identifier \($0)"
|
|
69
|
+
)
|
|
70
|
+
return nil
|
|
71
|
+
}, animated: true)
|
|
72
|
+
controller.navigationController?.setToolbarHidden(
|
|
73
|
+
false, animated: true)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
print(
|
|
78
|
+
"[expo-router] Warning: Could not find owning UIViewController for RouterToolbarHostView")
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
83
|
+
if let toolbarItem = childComponentView as? RouterToolbarItemView {
|
|
84
|
+
if toolbarItem.identifier.isEmpty {
|
|
85
|
+
print("[expo-router] RouterToolbarItemView identifier is empty")
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
addRouterToolbarItemAtIndex(toolbarItem, index: index)
|
|
89
|
+
} else if let menu = childComponentView as? LinkPreviewNativeActionView {
|
|
90
|
+
addMenuToolbarItemAtIndex(menu, index: index)
|
|
91
|
+
} else {
|
|
92
|
+
print(
|
|
93
|
+
"ExpoRouter: Unknown child component view (\(childComponentView)) mounted to RouterToolbarHost"
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
updateToolbarItems()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
100
|
+
if let toolbarItem = childComponentView as? RouterToolbarItemView {
|
|
101
|
+
if toolbarItem.identifier.isEmpty {
|
|
102
|
+
print("[expo-router] RouterToolbarItemView identifier is empty")
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
removeToolbarItemWithId(toolbarItem.identifier)
|
|
106
|
+
} else if let menu = childComponentView as? LinkPreviewNativeActionView {
|
|
107
|
+
if menu.identifier.isEmpty {
|
|
108
|
+
print("[expo-router] Menu identifier is empty")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
removeToolbarItemWithId(menu.identifier)
|
|
112
|
+
} else {
|
|
113
|
+
print(
|
|
114
|
+
"ExpoRouter: Unknown child component view (\(childComponentView)) unmounted from RouterToolbarHost"
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
updateToolbarItems()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override func didMoveToWindow() {
|
|
121
|
+
super.didMoveToWindow()
|
|
122
|
+
// Update toolbar items when the view is added to the window
|
|
123
|
+
updateToolbarItems()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func updateMenu() {
|
|
127
|
+
updateToolbarItems()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private func findViewController() -> RNSScreen? {
|
|
131
|
+
var responder: UIResponder? = self
|
|
132
|
+
while let r = responder {
|
|
133
|
+
if let r = r as? RNSScreen {
|
|
134
|
+
return r
|
|
135
|
+
}
|
|
136
|
+
responder = r.next
|
|
137
|
+
}
|
|
138
|
+
return nil
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import React
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
class RouterToolbarItemView: ExpoView {
|
|
6
|
+
var identifier: String = ""
|
|
7
|
+
@ReactiveProp var type: ItemType?
|
|
8
|
+
@ReactiveProp var title: String?
|
|
9
|
+
@ReactiveProp var systemImageName: String?
|
|
10
|
+
@ReactiveProp var customView: UIView?
|
|
11
|
+
@ReactiveProp var customTintColor: UIColor?
|
|
12
|
+
@ReactiveProp var hidesSharedBackground: Bool = false
|
|
13
|
+
@ReactiveProp var sharesBackground: Bool = true
|
|
14
|
+
@ReactiveProp var barButtonItemStyle: UIBarButtonItem.Style?
|
|
15
|
+
@ReactiveProp var width: Double?
|
|
16
|
+
// Using "routerHidden" to avoid conflict with UIView's "isHidden"
|
|
17
|
+
@ReactiveProp var routerHidden: Bool = false
|
|
18
|
+
@ReactiveProp var selected: Bool = false
|
|
19
|
+
@ReactiveProp var possibleTitles: Set<String>?
|
|
20
|
+
@ReactiveProp var badgeConfiguration: BadgeConfiguration?
|
|
21
|
+
|
|
22
|
+
var host: RouterToolbarHostView?
|
|
23
|
+
|
|
24
|
+
let onSelected = EventDispatcher()
|
|
25
|
+
|
|
26
|
+
func performUpdate() {
|
|
27
|
+
self.host?.updateToolbarItem(withId: self.identifier)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@objc func handleAction() {
|
|
31
|
+
onSelected()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var barButtonItem: UIBarButtonItem {
|
|
35
|
+
var item = UIBarButtonItem()
|
|
36
|
+
if let customView {
|
|
37
|
+
item = UIBarButtonItem(customView: customView)
|
|
38
|
+
} else if type == .fluidSpacer {
|
|
39
|
+
item = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
|
40
|
+
} else if type == .fixedSpacer {
|
|
41
|
+
item = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
|
|
42
|
+
} else {
|
|
43
|
+
if let title {
|
|
44
|
+
item.title = title
|
|
45
|
+
}
|
|
46
|
+
item.possibleTitles = possibleTitles
|
|
47
|
+
if let systemImageName {
|
|
48
|
+
item.image = UIImage(systemName: systemImageName)
|
|
49
|
+
}
|
|
50
|
+
if let tintColor = customTintColor {
|
|
51
|
+
item.tintColor = tintColor
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if #available(iOS 26.0, *) {
|
|
55
|
+
item.hidesSharedBackground = hidesSharedBackground
|
|
56
|
+
item.sharesBackground = sharesBackground
|
|
57
|
+
}
|
|
58
|
+
if let barButtonItemStyle {
|
|
59
|
+
item.style = barButtonItemStyle
|
|
60
|
+
}
|
|
61
|
+
item.target = self
|
|
62
|
+
item.action = #selector(handleAction)
|
|
63
|
+
if let width = width {
|
|
64
|
+
item.width = CGFloat(width)
|
|
65
|
+
}
|
|
66
|
+
if #available(iOS 16.0, *) {
|
|
67
|
+
item.isHidden = routerHidden
|
|
68
|
+
}
|
|
69
|
+
item.isSelected = selected
|
|
70
|
+
if #available(iOS 26.0, *) {
|
|
71
|
+
if let badgeConfig = badgeConfiguration {
|
|
72
|
+
var badge = UIBarButtonItem.Badge.indicator()
|
|
73
|
+
if let value = badgeConfig.value {
|
|
74
|
+
badge = .string(value)
|
|
75
|
+
}
|
|
76
|
+
if let backgroundColor = badgeConfig.backgroundColor {
|
|
77
|
+
badge.backgroundColor = backgroundColor
|
|
78
|
+
}
|
|
79
|
+
if let foregroundColor = badgeConfig.color {
|
|
80
|
+
badge.foregroundColor = foregroundColor
|
|
81
|
+
}
|
|
82
|
+
if badgeConfig.fontFamily != nil || badgeConfig.fontSize != nil
|
|
83
|
+
|| 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)
|
|
92
|
+
badge.font = font
|
|
93
|
+
}
|
|
94
|
+
// TODO: Find out why this does not work
|
|
95
|
+
item.badge = badge
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return item
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
required init(appContext: AppContext? = nil) {
|
|
103
|
+
super.init(appContext: appContext)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
107
|
+
if customView != nil {
|
|
108
|
+
print(
|
|
109
|
+
"[expo-router] Warning: RouterToolbarItemView can only have one child view"
|
|
110
|
+
)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
customView = childComponentView
|
|
114
|
+
performUpdate()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
118
|
+
if customView == childComponentView {
|
|
119
|
+
childComponentView.removeFromSuperview()
|
|
120
|
+
customView = nil
|
|
121
|
+
performUpdate()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
enum ItemType: String, Enumerable {
|
|
127
|
+
case normal
|
|
128
|
+
case fixedSpacer
|
|
129
|
+
case fluidSpacer
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
struct BadgeConfiguration: Equatable {
|
|
133
|
+
var value: String?
|
|
134
|
+
var backgroundColor: UIColor?
|
|
135
|
+
var color: UIColor?
|
|
136
|
+
var fontFamily: String?
|
|
137
|
+
var fontSize: Double?
|
|
138
|
+
var fontWeight: String?
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@propertyWrapper
|
|
142
|
+
struct ReactiveProp<Value: Equatable> {
|
|
143
|
+
private var value: Value
|
|
144
|
+
|
|
145
|
+
init(wrappedValue: Value) {
|
|
146
|
+
self.value = wrappedValue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static subscript<EnclosingSelf: RouterToolbarItemView>(
|
|
150
|
+
_enclosingInstance instance: EnclosingSelf,
|
|
151
|
+
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
|
|
152
|
+
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, ReactiveProp<Value>>
|
|
153
|
+
) -> Value {
|
|
154
|
+
get {
|
|
155
|
+
instance[keyPath: storageKeyPath].value
|
|
156
|
+
}
|
|
157
|
+
set {
|
|
158
|
+
let oldValue = instance[keyPath: storageKeyPath].value
|
|
159
|
+
if oldValue != newValue {
|
|
160
|
+
instance[keyPath: storageKeyPath].value = newValue
|
|
161
|
+
instance.performUpdate()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@available(*, unavailable, message: "Use the enclosing instance subscript.")
|
|
167
|
+
var wrappedValue: Value {
|
|
168
|
+
get { fatalError() }
|
|
169
|
+
set { fatalError() }
|
|
170
|
+
}
|
|
171
|
+
}
|