expo-router 5.2.0-canary-20250630-547cd82 → 5.2.0-canary-20250709-136b77f
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/assets/modal.module.css +124 -0
- package/build/fork/useLinking.d.ts.map +1 -1
- package/build/fork/useLinking.js +7 -3
- package/build/fork/useLinking.js.map +1 -1
- package/build/layouts/DrawerClient.d.ts +2 -2
- package/build/layouts/Stack.web.d.ts +17 -0
- package/build/layouts/Stack.web.d.ts.map +1 -0
- package/build/layouts/Stack.web.js +17 -0
- package/build/layouts/Stack.web.js.map +1 -0
- package/build/layouts/StackClient.d.ts +41 -3
- package/build/layouts/StackClient.d.ts.map +1 -1
- package/build/layouts/StackClient.js +79 -2
- package/build/layouts/StackClient.js.map +1 -1
- package/build/layouts/TabsClient.d.ts +3 -3
- package/build/link/BaseExpoRouterLink.d.ts +2 -1
- package/build/link/BaseExpoRouterLink.d.ts.map +1 -1
- package/build/link/BaseExpoRouterLink.js +37 -1
- package/build/link/BaseExpoRouterLink.js.map +1 -1
- package/build/link/ExpoLink.d.ts +2 -1
- package/build/link/ExpoLink.d.ts.map +1 -1
- package/build/link/ExpoLink.js +44 -3
- package/build/link/ExpoLink.js.map +1 -1
- package/build/link/Link.d.ts +3 -3
- package/build/link/Link.d.ts.map +1 -1
- package/build/link/Link.js.map +1 -1
- package/build/link/LinkWithPreview.d.ts +19 -6
- package/build/link/LinkWithPreview.d.ts.map +1 -1
- package/build/link/LinkWithPreview.js +53 -18
- package/build/link/LinkWithPreview.js.map +1 -1
- package/build/link/preview/HrefPreview.d.ts.map +1 -1
- package/build/link/preview/HrefPreview.js +2 -2
- package/build/link/preview/HrefPreview.js.map +1 -1
- package/build/link/preview/native.d.ts +2 -0
- package/build/link/preview/native.d.ts.map +1 -1
- package/build/link/preview/native.js.map +1 -1
- package/build/modal/web/ModalStack.web.d.ts +25 -0
- package/build/modal/web/ModalStack.web.d.ts.map +1 -0
- package/build/modal/web/ModalStack.web.js +86 -0
- package/build/modal/web/ModalStack.web.js.map +1 -0
- package/build/modal/web/ModalStackRouteDrawer.web.d.ts +14 -0
- package/build/modal/web/ModalStackRouteDrawer.web.d.ts.map +1 -0
- package/build/modal/web/ModalStackRouteDrawer.web.js +161 -0
- package/build/modal/web/ModalStackRouteDrawer.web.js.map +1 -0
- package/build/modal/web/TransparentModalStackRouteDrawer.web.d.ts +10 -0
- package/build/modal/web/TransparentModalStackRouteDrawer.web.d.ts.map +1 -0
- package/build/modal/web/TransparentModalStackRouteDrawer.web.js +28 -0
- package/build/modal/web/TransparentModalStackRouteDrawer.web.js.map +1 -0
- package/build/modal/web/modalStyles.d.ts +3 -0
- package/build/modal/web/modalStyles.d.ts.map +1 -0
- package/build/modal/web/modalStyles.js +8 -0
- package/build/modal/web/modalStyles.js.map +1 -0
- package/build/modal/web/types.d.ts +14 -0
- package/build/modal/web/types.d.ts.map +1 -0
- package/build/modal/web/types.js +3 -0
- package/build/modal/web/types.js.map +1 -0
- package/build/modal/web/utils.d.ts +70 -0
- package/build/modal/web/utils.d.ts.map +1 -0
- package/build/modal/web/utils.js +117 -0
- package/build/modal/web/utils.js.map +1 -0
- package/ios/ExpoHead.podspec +1 -1
- package/ios/LinkPreview/LinkPreviewNativeActionView.swift +22 -0
- package/ios/LinkPreview/LinkPreviewNativeModule.swift +3 -0
- package/ios/LinkPreview/LinkPreviewNativeView.swift +30 -11
- package/package.json +9 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modalStyles.js","sourceRoot":"","sources":["../../../src/modal/web/modalStyles.ts"],"names":[],"mappings":";;;;;AAAA,wFAAsD;AAEtD,kBAAe,0BAAM,CAAC","sourcesContent":["import styles from '../../../assets/modal.module.css';\n\nexport default styles;\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ParamListBase, StackActionHelpers, StackNavigationState, StackRouterOptions, useNavigationBuilder } from '@react-navigation/native';
|
|
2
|
+
import { NativeStackNavigationEventMap, NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
|
3
|
+
import { ExtendedStackNavigationOptions } from '../../layouts/StackClient';
|
|
4
|
+
export type ModalStackNavigatorProps = {
|
|
5
|
+
initialRouteName?: string;
|
|
6
|
+
screenOptions?: ExtendedStackNavigationOptions;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export type ModalStackViewProps = Omit<ReturnType<typeof useNavigationBuilder<StackNavigationState<ParamListBase>, StackRouterOptions, StackActionHelpers<ParamListBase>, NativeStackNavigationOptions, NativeStackNavigationEventMap>>, 'NavigationContent'>;
|
|
10
|
+
export type CSSWithVars = React.CSSProperties & {
|
|
11
|
+
[key: `--${string}`]: string | number;
|
|
12
|
+
};
|
|
13
|
+
export type PresentationOptions = Partial<Pick<ExtendedStackNavigationOptions, 'presentation'>>;
|
|
14
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modal/web/types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC7B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAE3E,MAAM,MAAM,wBAAwB,GAAG;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,8BAA8B,CAAC;IAC/C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,UAAU,CACR,OAAO,oBAAoB,CACzB,oBAAoB,CAAC,aAAa,CAAC,EACnC,kBAAkB,EAClB,kBAAkB,CAAC,aAAa,CAAC,EACjC,4BAA4B,EAC5B,6BAA6B,CAC9B,CACF,EACD,mBAAmB,CACpB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,GAAG;IAC9C,CAAC,GAAG,EAAE,KAAK,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,cAAc,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modal/web/types.ts"],"names":[],"mappings":"","sourcesContent":["import {\n ParamListBase,\n StackActionHelpers,\n StackNavigationState,\n StackRouterOptions,\n useNavigationBuilder,\n} from '@react-navigation/native';\nimport {\n NativeStackNavigationEventMap,\n NativeStackNavigationOptions,\n} from '@react-navigation/native-stack';\n\nimport { ExtendedStackNavigationOptions } from '../../layouts/StackClient';\n\nexport type ModalStackNavigatorProps = {\n initialRouteName?: string;\n screenOptions?: ExtendedStackNavigationOptions;\n children: React.ReactNode;\n};\n\nexport type ModalStackViewProps = Omit<\n ReturnType<\n typeof useNavigationBuilder<\n StackNavigationState<ParamListBase>,\n StackRouterOptions,\n StackActionHelpers<ParamListBase>,\n NativeStackNavigationOptions,\n NativeStackNavigationEventMap\n >\n >,\n 'NavigationContent'\n>;\n\nexport type CSSWithVars = React.CSSProperties & {\n [key: `--${string}`]: string | number;\n};\n\nexport type PresentationOptions = Partial<Pick<ExtendedStackNavigationOptions, 'presentation'>>;\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ParamListBase, StackNavigationState } from '@react-navigation/native';
|
|
2
|
+
import { ExtendedStackNavigationOptions } from '../../layouts/StackClient';
|
|
3
|
+
/**
|
|
4
|
+
* A minimal subset of `ExtendedStackNavigationOptions` needed for the helper
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type PresentationOptions = Partial<Pick<ExtendedStackNavigationOptions, 'presentation'>>;
|
|
8
|
+
/**
|
|
9
|
+
* Helper to determine if a given screen should be treated as a modal-type presentation
|
|
10
|
+
*
|
|
11
|
+
* @param options - The navigation options.
|
|
12
|
+
* @returns Whether the screen should be treated as a modal-type presentation.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare function isModalPresentation(options?: PresentationOptions | null): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Helper to determine if a given screen should be treated as a transparent modal-type presentation
|
|
19
|
+
*
|
|
20
|
+
* @param options - The navigation options.
|
|
21
|
+
* @returns Whether the screen should be treated as a transparent modal-type presentation.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare function isTransparentModalPresentation(options?: PresentationOptions | null): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* SSR-safe viewport detection: initial render always returns `false` so that
|
|
28
|
+
* server and client markup match. The actual media query evaluation happens
|
|
29
|
+
* after mount.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function useIsDesktop(breakpoint?: number): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Returns a copy of the given Stack navigation state with any modal-type routes removed
|
|
36
|
+
* (only when running on the web) and a recalculated `index` that still points at the
|
|
37
|
+
* currently active non-modal route. If the active route *is* a modal that gets
|
|
38
|
+
* filtered out, we fall back to the last remaining route – this matches the logic
|
|
39
|
+
* used inside `ModalStackView` so that the underlying `NativeStackView` never tries
|
|
40
|
+
* to render a modal screen that is simultaneously being shown in the overlay.
|
|
41
|
+
*
|
|
42
|
+
* This helper is exported primarily for unit-testing; it should be considered
|
|
43
|
+
* internal to `ModalStack.web` and not a public API.
|
|
44
|
+
*
|
|
45
|
+
* @param state - The navigation state.
|
|
46
|
+
* @param descriptors - The navigation descriptors.
|
|
47
|
+
* @param isWeb - Whether the current platform is web.
|
|
48
|
+
* @returns The navigation state with any modal-type routes removed.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export declare function convertStackStateToNonModalState(state: StackNavigationState<ParamListBase>, descriptors: Record<string, {
|
|
53
|
+
options: ExtendedStackNavigationOptions;
|
|
54
|
+
}>, isWeb: boolean): {
|
|
55
|
+
routes: import("@react-navigation/native").NavigationRoute<ParamListBase, string>[];
|
|
56
|
+
index: number;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Returns the index of the last route in the stack that is *not* a modal.
|
|
60
|
+
*
|
|
61
|
+
* @param state - The navigation state.
|
|
62
|
+
* @param descriptors - The navigation descriptors.
|
|
63
|
+
* @returns The index of the last non-modal route.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export declare function findLastNonModalIndex(state: StackNavigationState<ParamListBase>, descriptors: Record<string, {
|
|
68
|
+
options: ExtendedStackNavigationOptions;
|
|
69
|
+
}>): number;
|
|
70
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/modal/web/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAG/E,OAAO,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,cAAc,CAAC,CAAC,CAAC;AAEhG;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,WAUvE;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,WAGlF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,GAAE,MAAY,WAoBpD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,oBAAoB,CAAC,aAAa,CAAC,EAC1C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,8BAA8B,CAAA;CAAE,CAAC,EACxE,KAAK,EAAE,OAAO;;;EAmBf;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,oBAAoB,CAAC,aAAa,CAAC,EAC1C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,8BAA8B,CAAA;CAAE,CAAC,UASzE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isModalPresentation = isModalPresentation;
|
|
8
|
+
exports.isTransparentModalPresentation = isTransparentModalPresentation;
|
|
9
|
+
exports.useIsDesktop = useIsDesktop;
|
|
10
|
+
exports.convertStackStateToNonModalState = convertStackStateToNonModalState;
|
|
11
|
+
exports.findLastNonModalIndex = findLastNonModalIndex;
|
|
12
|
+
const react_1 = __importDefault(require("react"));
|
|
13
|
+
/**
|
|
14
|
+
* Helper to determine if a given screen should be treated as a modal-type presentation
|
|
15
|
+
*
|
|
16
|
+
* @param options - The navigation options.
|
|
17
|
+
* @returns Whether the screen should be treated as a modal-type presentation.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
function isModalPresentation(options) {
|
|
22
|
+
const presentation = options?.presentation;
|
|
23
|
+
return (presentation === 'modal' ||
|
|
24
|
+
presentation === 'formSheet' ||
|
|
25
|
+
presentation === 'fullScreenModal' ||
|
|
26
|
+
presentation === 'containedModal' ||
|
|
27
|
+
presentation === 'transparentModal' ||
|
|
28
|
+
presentation === 'containedTransparentModal');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Helper to determine if a given screen should be treated as a transparent modal-type presentation
|
|
32
|
+
*
|
|
33
|
+
* @param options - The navigation options.
|
|
34
|
+
* @returns Whether the screen should be treated as a transparent modal-type presentation.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
function isTransparentModalPresentation(options) {
|
|
39
|
+
const presentation = options?.presentation;
|
|
40
|
+
return presentation === 'transparentModal' || presentation === 'containedTransparentModal';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* SSR-safe viewport detection: initial render always returns `false` so that
|
|
44
|
+
* server and client markup match. The actual media query evaluation happens
|
|
45
|
+
* after mount.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
function useIsDesktop(breakpoint = 768) {
|
|
50
|
+
const isWeb = process.env.EXPO_OS === 'web';
|
|
51
|
+
// Ensure server-side and initial client render agree (mobile first).
|
|
52
|
+
const [isDesktop, setIsDesktop] = react_1.default.useState(false);
|
|
53
|
+
react_1.default.useEffect(() => {
|
|
54
|
+
if (!isWeb || typeof window === 'undefined')
|
|
55
|
+
return;
|
|
56
|
+
const mql = window.matchMedia(`(min-width: ${breakpoint}px)`);
|
|
57
|
+
const listener = (e) => setIsDesktop(e.matches);
|
|
58
|
+
// Update immediately after mount
|
|
59
|
+
setIsDesktop(mql.matches);
|
|
60
|
+
mql.addEventListener('change', listener);
|
|
61
|
+
return () => mql.removeEventListener('change', listener);
|
|
62
|
+
}, [breakpoint, isWeb]);
|
|
63
|
+
return isDesktop;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns a copy of the given Stack navigation state with any modal-type routes removed
|
|
67
|
+
* (only when running on the web) and a recalculated `index` that still points at the
|
|
68
|
+
* currently active non-modal route. If the active route *is* a modal that gets
|
|
69
|
+
* filtered out, we fall back to the last remaining route – this matches the logic
|
|
70
|
+
* used inside `ModalStackView` so that the underlying `NativeStackView` never tries
|
|
71
|
+
* to render a modal screen that is simultaneously being shown in the overlay.
|
|
72
|
+
*
|
|
73
|
+
* This helper is exported primarily for unit-testing; it should be considered
|
|
74
|
+
* internal to `ModalStack.web` and not a public API.
|
|
75
|
+
*
|
|
76
|
+
* @param state - The navigation state.
|
|
77
|
+
* @param descriptors - The navigation descriptors.
|
|
78
|
+
* @param isWeb - Whether the current platform is web.
|
|
79
|
+
* @returns The navigation state with any modal-type routes removed.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
function convertStackStateToNonModalState(state, descriptors, isWeb) {
|
|
84
|
+
if (!isWeb) {
|
|
85
|
+
return { routes: state.routes, index: state.index };
|
|
86
|
+
}
|
|
87
|
+
// Remove every modal-type route from the stack on web.
|
|
88
|
+
const routes = state.routes.filter((route) => {
|
|
89
|
+
return !isModalPresentation(descriptors[route.key].options);
|
|
90
|
+
});
|
|
91
|
+
// Recalculate the active index so it still points at the same non-modal route, or –
|
|
92
|
+
// if that route was filtered out – at the last remaining route.
|
|
93
|
+
let index = routes.findIndex((r) => r.key === state.routes[state.index]?.key);
|
|
94
|
+
if (index < 0) {
|
|
95
|
+
index = routes.length > 0 ? routes.length - 1 : 0;
|
|
96
|
+
}
|
|
97
|
+
return { routes, index };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns the index of the last route in the stack that is *not* a modal.
|
|
101
|
+
*
|
|
102
|
+
* @param state - The navigation state.
|
|
103
|
+
* @param descriptors - The navigation descriptors.
|
|
104
|
+
* @returns The index of the last non-modal route.
|
|
105
|
+
*
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
function findLastNonModalIndex(state, descriptors) {
|
|
109
|
+
// Iterate backwards through the stack to find the last non-modal route.
|
|
110
|
+
for (let i = state.routes.length - 1; i >= 0; i--) {
|
|
111
|
+
if (!isModalPresentation(descriptors[state.routes[i].key].options)) {
|
|
112
|
+
return i;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return -1;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/modal/web/utils.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\nimport { ParamListBase, StackNavigationState } from '@react-navigation/native';\nimport React from 'react';\n\nimport { ExtendedStackNavigationOptions } from '../../layouts/StackClient';\n\n/**\n * A minimal subset of `ExtendedStackNavigationOptions` needed for the helper\n * @internal\n */\nexport type PresentationOptions = Partial<Pick<ExtendedStackNavigationOptions, 'presentation'>>;\n\n/**\n * Helper to determine if a given screen should be treated as a modal-type presentation\n *\n * @param options - The navigation options.\n * @returns Whether the screen should be treated as a modal-type presentation.\n *\n * @internal\n */\nexport function isModalPresentation(options?: PresentationOptions | null) {\n const presentation = options?.presentation;\n return (\n presentation === 'modal' ||\n presentation === 'formSheet' ||\n presentation === 'fullScreenModal' ||\n presentation === 'containedModal' ||\n presentation === 'transparentModal' ||\n presentation === 'containedTransparentModal'\n );\n}\n\n/**\n * Helper to determine if a given screen should be treated as a transparent modal-type presentation\n *\n * @param options - The navigation options.\n * @returns Whether the screen should be treated as a transparent modal-type presentation.\n *\n * @internal\n */\nexport function isTransparentModalPresentation(options?: PresentationOptions | null) {\n const presentation = options?.presentation;\n return presentation === 'transparentModal' || presentation === 'containedTransparentModal';\n}\n\n/**\n * SSR-safe viewport detection: initial render always returns `false` so that\n * server and client markup match. The actual media query evaluation happens\n * after mount.\n *\n * @internal\n */\nexport function useIsDesktop(breakpoint: number = 768) {\n const isWeb = process.env.EXPO_OS === 'web';\n\n // Ensure server-side and initial client render agree (mobile first).\n const [isDesktop, setIsDesktop] = React.useState<boolean>(false);\n\n React.useEffect(() => {\n if (!isWeb || typeof window === 'undefined') return;\n\n const mql = window.matchMedia(`(min-width: ${breakpoint}px)`);\n const listener = (e: MediaQueryListEvent) => setIsDesktop(e.matches);\n\n // Update immediately after mount\n setIsDesktop(mql.matches);\n\n mql.addEventListener('change', listener);\n return () => mql.removeEventListener('change', listener);\n }, [breakpoint, isWeb]);\n\n return isDesktop;\n}\n\n/**\n * Returns a copy of the given Stack navigation state with any modal-type routes removed\n * (only when running on the web) and a recalculated `index` that still points at the\n * currently active non-modal route. If the active route *is* a modal that gets\n * filtered out, we fall back to the last remaining route – this matches the logic\n * used inside `ModalStackView` so that the underlying `NativeStackView` never tries\n * to render a modal screen that is simultaneously being shown in the overlay.\n *\n * This helper is exported primarily for unit-testing; it should be considered\n * internal to `ModalStack.web` and not a public API.\n *\n * @param state - The navigation state.\n * @param descriptors - The navigation descriptors.\n * @param isWeb - Whether the current platform is web.\n * @returns The navigation state with any modal-type routes removed.\n *\n * @internal\n */\nexport function convertStackStateToNonModalState(\n state: StackNavigationState<ParamListBase>,\n descriptors: Record<string, { options: ExtendedStackNavigationOptions }>,\n isWeb: boolean\n) {\n if (!isWeb) {\n return { routes: state.routes, index: state.index };\n }\n\n // Remove every modal-type route from the stack on web.\n const routes = state.routes.filter((route) => {\n return !isModalPresentation(descriptors[route.key].options);\n });\n\n // Recalculate the active index so it still points at the same non-modal route, or –\n // if that route was filtered out – at the last remaining route.\n let index = routes.findIndex((r) => r.key === state.routes[state.index]?.key);\n if (index < 0) {\n index = routes.length > 0 ? routes.length - 1 : 0;\n }\n\n return { routes, index };\n}\n\n/**\n * Returns the index of the last route in the stack that is *not* a modal.\n *\n * @param state - The navigation state.\n * @param descriptors - The navigation descriptors.\n * @returns The index of the last non-modal route.\n *\n * @internal\n */\nexport function findLastNonModalIndex(\n state: StackNavigationState<ParamListBase>,\n descriptors: Record<string, { options: ExtendedStackNavigationOptions }>\n) {\n // Iterate backwards through the stack to find the last non-modal route.\n for (let i = state.routes.length - 1; i >= 0; i--) {\n if (!isModalPresentation(descriptors[state.routes[i].key].options)) {\n return i;\n }\n }\n return -1;\n}\n"]}
|
package/ios/ExpoHead.podspec
CHANGED
|
@@ -4,9 +4,31 @@ import WebKit
|
|
|
4
4
|
class LinkPreviewNativeActionView: ExpoView {
|
|
5
5
|
var id: String = ""
|
|
6
6
|
var title: String = ""
|
|
7
|
+
var icon: String?
|
|
8
|
+
var subActions: [LinkPreviewNativeActionView] = []
|
|
7
9
|
|
|
8
10
|
required init(appContext: AppContext? = nil) {
|
|
9
11
|
super.init(appContext: appContext)
|
|
10
12
|
clipsToBounds = true
|
|
11
13
|
}
|
|
14
|
+
|
|
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
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
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
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
12
34
|
}
|
|
@@ -43,6 +43,9 @@ public class LinkPreviewNativeModule: Module {
|
|
|
43
43
|
Prop("title") { (view: LinkPreviewNativeActionView, title: String) in
|
|
44
44
|
view.title = title
|
|
45
45
|
}
|
|
46
|
+
Prop("icon") { (view: LinkPreviewNativeActionView, icon: String) in
|
|
47
|
+
view.icon = icon
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
View(NativeLinkPreviewTrigger.self) {}
|
|
@@ -25,7 +25,7 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate {
|
|
|
25
25
|
|
|
26
26
|
func setNextScreenId(_ screenId: String) {
|
|
27
27
|
self.nextScreenId = screenId
|
|
28
|
-
|
|
28
|
+
linkPreviewNativeNavigation.updatePreloadedView(screenId, with: self)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// MARK: - Children
|
|
@@ -138,8 +138,8 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate {
|
|
|
138
138
|
animator: UIContextMenuInteractionCommitAnimating
|
|
139
139
|
) {
|
|
140
140
|
linkPreviewNativeNavigation.pushPreloadedView()
|
|
141
|
+
onPreviewTapped()
|
|
141
142
|
animator.addCompletion { [weak self] in
|
|
142
|
-
self?.onPreviewTapped()
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -159,17 +159,36 @@ class NativeLinkPreviewView: ExpoView, UIContextMenuInteractionDelegate {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
private func createContextMenu() -> UIMenu {
|
|
162
|
-
let
|
|
163
|
-
return
|
|
164
|
-
title: action.title
|
|
165
|
-
) { _ in
|
|
166
|
-
self.onActionSelected([
|
|
167
|
-
"id": action.id
|
|
168
|
-
])
|
|
169
|
-
}
|
|
162
|
+
if actions.count == 1, let menu = convertActionViewToUiAction(actions[0]) as? UIMenu {
|
|
163
|
+
return menu
|
|
170
164
|
}
|
|
165
|
+
return UIMenu(
|
|
166
|
+
title: "",
|
|
167
|
+
children: actions.map { action in
|
|
168
|
+
self.convertActionViewToUiAction(action)
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
private func convertActionViewToUiAction(_ action: LinkPreviewNativeActionView) -> UIMenuElement {
|
|
174
|
+
if !action.subActions.isEmpty {
|
|
175
|
+
let subActions = action.subActions.map { subAction in
|
|
176
|
+
self.convertActionViewToUiAction(subAction)
|
|
177
|
+
}
|
|
178
|
+
return UIMenu(
|
|
179
|
+
title: action.title,
|
|
180
|
+
image: action.icon.flatMap { UIImage(systemName: $0) },
|
|
181
|
+
children: subActions
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
return UIAction(
|
|
185
|
+
title: action.title,
|
|
186
|
+
image: action.icon.flatMap { UIImage(systemName: $0) }
|
|
187
|
+
) { _ in
|
|
188
|
+
self.onActionSelected([
|
|
189
|
+
"id": action.id
|
|
190
|
+
])
|
|
191
|
+
}
|
|
173
192
|
}
|
|
174
193
|
}
|
|
175
194
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-router",
|
|
3
|
-
"version": "5.2.0-canary-
|
|
3
|
+
"version": "5.2.0-canary-20250709-136b77f",
|
|
4
4
|
"description": "Expo Router is a file-based router for React Native and web applications.",
|
|
5
5
|
"author": "650 Industries, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
],
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"@react-navigation/drawer": "^7.3.9",
|
|
79
|
-
"expo": "54.0.0-canary-
|
|
80
|
-
"expo-constants": "17.1.
|
|
81
|
-
"expo-linking": "7.1.
|
|
79
|
+
"expo": "54.0.0-canary-20250709-136b77f",
|
|
80
|
+
"expo-constants": "17.1.8-canary-20250709-136b77f",
|
|
81
|
+
"expo-linking": "7.1.8-canary-20250709-136b77f",
|
|
82
82
|
"react-native-reanimated": "*",
|
|
83
83
|
"react-native-safe-area-context": "*",
|
|
84
84
|
"react-native-screens": "*"
|
|
@@ -105,8 +105,8 @@
|
|
|
105
105
|
"tsd": "^0.28.1"
|
|
106
106
|
},
|
|
107
107
|
"dependencies": {
|
|
108
|
-
"@expo/metro-runtime": "6.0.0-canary-
|
|
109
|
-
"@expo/server": "0.6.4-canary-
|
|
108
|
+
"@expo/metro-runtime": "6.0.0-canary-20250709-136b77f",
|
|
109
|
+
"@expo/server": "0.6.4-canary-20250709-136b77f",
|
|
110
110
|
"@radix-ui/react-slot": "1.2.0",
|
|
111
111
|
"@react-navigation/bottom-tabs": "^7.3.10",
|
|
112
112
|
"@react-navigation/native": "^7.1.6",
|
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
"schema-utils": "^4.0.1",
|
|
119
119
|
"semver": "~7.6.3",
|
|
120
120
|
"server-only": "^0.0.1",
|
|
121
|
-
"
|
|
121
|
+
"sf-symbols-typescript": "^2.1.0",
|
|
122
|
+
"shallowequal": "^1.1.0",
|
|
123
|
+
"vaul": "^0.9.0"
|
|
122
124
|
}
|
|
123
125
|
}
|