expo-router 5.2.0-canary-20250701-6a945c5 → 5.2.0-canary-20250713-8f814f8
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 +47 -17
- 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/Modal.d.ts +2 -0
- package/build/modal/Modal.d.ts.map +1 -1
- package/build/modal/Modal.js +9 -0
- package/build/modal/Modal.js.map +1 -1
- package/build/modal/utils.d.ts +3 -0
- package/build/modal/utils.d.ts.map +1 -0
- package/build/modal/utils.js +14 -0
- package/build/modal/utils.js.map +1 -0
- 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,161 @@
|
|
|
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.ModalStackRouteDrawer = ModalStackRouteDrawer;
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const vaul_1 = require("vaul");
|
|
10
|
+
const modalStyles_1 = __importDefault(require("./modalStyles"));
|
|
11
|
+
const utils_1 = require("./utils");
|
|
12
|
+
function ModalStackRouteDrawer({ routeKey, options, renderScreen, onDismiss, themeColors, }) {
|
|
13
|
+
const [open, setOpen] = react_1.default.useState(true);
|
|
14
|
+
// Determine sheet vs. modal with an SSR-safe hook. The first render (during
|
|
15
|
+
// hydration) always assumes mobile/sheet to match the server markup; an
|
|
16
|
+
// effect then updates the state after mount if the viewport is desktop.
|
|
17
|
+
const isDesktop = (0, utils_1.useIsDesktop)();
|
|
18
|
+
const isSheet = !isDesktop;
|
|
19
|
+
// Resolve snap points logic.
|
|
20
|
+
const allowed = options.sheetAllowedDetents;
|
|
21
|
+
const isArrayDetents = Array.isArray(allowed);
|
|
22
|
+
const useCustomSnapPoints = isArrayDetents && !(allowed.length === 1 && allowed[0] === 1);
|
|
23
|
+
let snapPoints = useCustomSnapPoints
|
|
24
|
+
? allowed
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!isSheet) {
|
|
27
|
+
snapPoints = [1];
|
|
28
|
+
}
|
|
29
|
+
const [snap, setSnap] = react_1.default.useState(useCustomSnapPoints && isArrayDetents ? allowed[0] : 1);
|
|
30
|
+
// Update the snap value when custom snap points change.
|
|
31
|
+
react_1.default.useEffect(() => {
|
|
32
|
+
if (isSheet) {
|
|
33
|
+
const next = useCustomSnapPoints && isArrayDetents ? allowed[0] : 1;
|
|
34
|
+
setSnap(next);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Desktop modal always fixed snap at 1
|
|
38
|
+
setSnap(1);
|
|
39
|
+
}
|
|
40
|
+
}, [isSheet, useCustomSnapPoints, isArrayDetents, allowed]);
|
|
41
|
+
// Map react-native-screens ios sheet undimmed logic to Vaul's fadeFromIndex
|
|
42
|
+
const fadeFromIndex = isSheet
|
|
43
|
+
? options.sheetLargestUndimmedDetentIndex === 'last'
|
|
44
|
+
? (snapPoints?.length ?? 0)
|
|
45
|
+
: typeof options.sheetLargestUndimmedDetentIndex === 'number'
|
|
46
|
+
? options.sheetLargestUndimmedDetentIndex + 1
|
|
47
|
+
: 0
|
|
48
|
+
: 0;
|
|
49
|
+
// --- Styling -----------------------------------------------------------
|
|
50
|
+
// Using CSS variables so defaults live in CSS and can be overridden via props.
|
|
51
|
+
const modalStyleVars = {
|
|
52
|
+
backgroundColor: themeColors.background,
|
|
53
|
+
};
|
|
54
|
+
if (!isSheet) {
|
|
55
|
+
if (options.webModalStyle?.width) {
|
|
56
|
+
modalStyleVars['--expo-router-modal-width'] =
|
|
57
|
+
typeof options.webModalStyle.width === 'number'
|
|
58
|
+
? `${options.webModalStyle.width}px`
|
|
59
|
+
: options.webModalStyle.width;
|
|
60
|
+
modalStyleVars['--expo-router-modal-max-width'] =
|
|
61
|
+
typeof options.webModalStyle.width === 'number'
|
|
62
|
+
? `${options.webModalStyle.width}px`
|
|
63
|
+
: options.webModalStyle.width;
|
|
64
|
+
// Also set explicit width so browsers that ignore CSS vars in `width` prop still work.
|
|
65
|
+
modalStyleVars.width =
|
|
66
|
+
typeof options.webModalStyle.width === 'number'
|
|
67
|
+
? `${options.webModalStyle.width}px`
|
|
68
|
+
: options.webModalStyle.width;
|
|
69
|
+
}
|
|
70
|
+
// Min width override
|
|
71
|
+
if (options.webModalStyle?.minWidth) {
|
|
72
|
+
const mw = typeof options.webModalStyle.minWidth === 'number'
|
|
73
|
+
? `${options.webModalStyle.minWidth}px`
|
|
74
|
+
: options.webModalStyle.minWidth;
|
|
75
|
+
modalStyleVars['--expo-router-modal-min-width'] = mw;
|
|
76
|
+
modalStyleVars.minWidth = mw;
|
|
77
|
+
}
|
|
78
|
+
if (options.webModalStyle?.height) {
|
|
79
|
+
const h = typeof options.webModalStyle.height === 'number'
|
|
80
|
+
? `${options.webModalStyle.height}px`
|
|
81
|
+
: options.webModalStyle.height;
|
|
82
|
+
modalStyleVars['--expo-router-modal-height'] = h;
|
|
83
|
+
modalStyleVars.maxHeight = h;
|
|
84
|
+
modalStyleVars.height = h;
|
|
85
|
+
modalStyleVars.minHeight = h;
|
|
86
|
+
}
|
|
87
|
+
// Separate min-height override (takes precedence over modalHeight)
|
|
88
|
+
if (options.webModalStyle?.minHeight) {
|
|
89
|
+
const mh = typeof options.webModalStyle.minHeight === 'number'
|
|
90
|
+
? `${options.webModalStyle.minHeight}px`
|
|
91
|
+
: options.webModalStyle.minHeight;
|
|
92
|
+
modalStyleVars['--expo-router-modal-min-height'] = mh;
|
|
93
|
+
modalStyleVars.minHeight = mh;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const fitToContents = isSheet && options.sheetAllowedDetents === 'fitToContents';
|
|
97
|
+
if (fitToContents) {
|
|
98
|
+
modalStyleVars.height = 'auto';
|
|
99
|
+
modalStyleVars.minHeight = 'auto';
|
|
100
|
+
// Allow sheet to grow with content but never exceed viewport height
|
|
101
|
+
modalStyleVars.maxHeight = 'calc(100vh)';
|
|
102
|
+
}
|
|
103
|
+
// Apply corner radius (default 10px)
|
|
104
|
+
const radiusValue = options.sheetCornerRadius ?? 10;
|
|
105
|
+
const radiusCss = typeof radiusValue === 'number' ? `${radiusValue}px` : radiusValue;
|
|
106
|
+
if (options.webModalStyle?.border) {
|
|
107
|
+
modalStyleVars['--expo-router-modal-border'] = options.webModalStyle.border;
|
|
108
|
+
}
|
|
109
|
+
if (isSheet) {
|
|
110
|
+
// Only top corners for mobile sheet
|
|
111
|
+
modalStyleVars.borderTopLeftRadius = radiusCss;
|
|
112
|
+
modalStyleVars.borderTopRightRadius = radiusCss;
|
|
113
|
+
// Only apply CSS var override if a custom corner radius was provided
|
|
114
|
+
if (options.sheetCornerRadius) {
|
|
115
|
+
modalStyleVars['--expo-router-modal-border-radius'] = radiusCss;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// All corners for desktop modal
|
|
120
|
+
if (options.sheetCornerRadius) {
|
|
121
|
+
modalStyleVars.borderRadius = radiusCss;
|
|
122
|
+
modalStyleVars['--expo-router-modal-border-radius'] = radiusCss;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// --- End Styling -----------------------------------------------------------
|
|
126
|
+
const handleOpenChange = (open) => {
|
|
127
|
+
if (!open)
|
|
128
|
+
onDismiss();
|
|
129
|
+
};
|
|
130
|
+
// Props that only make sense for sheets
|
|
131
|
+
const sheetProps = isSheet
|
|
132
|
+
? {
|
|
133
|
+
snapPoints: snapPoints,
|
|
134
|
+
activeSnapPoint: snap,
|
|
135
|
+
setActiveSnapPoint: setSnap,
|
|
136
|
+
fadeFromIndex,
|
|
137
|
+
}
|
|
138
|
+
: {};
|
|
139
|
+
return (<vaul_1.Drawer.Root key={`${routeKey}-${isSheet ? 'sheet' : 'modal'}`} open={open} dismissible={options.gestureEnabled ?? true} onAnimationEnd={handleOpenChange} shouldScaleBackground autoFocus onOpenChange={setOpen} {...sheetProps}>
|
|
140
|
+
<vaul_1.Drawer.Portal>
|
|
141
|
+
<vaul_1.Drawer.Overlay className={modalStyles_1.default.overlay} style={options.webModalStyle?.overlayBackground
|
|
142
|
+
? {
|
|
143
|
+
'--expo-router-modal-overlay-background': options.webModalStyle.overlayBackground,
|
|
144
|
+
}
|
|
145
|
+
: undefined}/>
|
|
146
|
+
<vaul_1.Drawer.Content aria-describedby="modal-description" className={modalStyles_1.default.drawerContent} style={{
|
|
147
|
+
pointerEvents: 'none',
|
|
148
|
+
...(fitToContents ? { height: 'auto' } : null),
|
|
149
|
+
}}>
|
|
150
|
+
<div className={modalStyles_1.default.modal} data-presentation={isSheet ? 'formSheet' : 'modal'} style={modalStyleVars}>
|
|
151
|
+
{/* TODO:(@Hirbod) Figure out how to add title and description to the modal for screen readers in a meaningful way */}
|
|
152
|
+
<vaul_1.Drawer.Title about="" aria-describedby="" className={modalStyles_1.default.srOnly}/>
|
|
153
|
+
<vaul_1.Drawer.Description about="" className={modalStyles_1.default.srOnly}/>
|
|
154
|
+
{/* Render the screen content */}
|
|
155
|
+
<div className={modalStyles_1.default.modalBody}>{renderScreen()}</div>
|
|
156
|
+
</div>
|
|
157
|
+
</vaul_1.Drawer.Content>
|
|
158
|
+
</vaul_1.Drawer.Portal>
|
|
159
|
+
</vaul_1.Drawer.Root>);
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=ModalStackRouteDrawer.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalStackRouteDrawer.web.js","sourceRoot":"","sources":["../../../src/modal/web/ModalStackRouteDrawer.web.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\nimport React from 'react';\nimport { Drawer } from 'vaul';\n\nimport modalStyles from './modalStyles';\nimport { CSSWithVars } from './types';\nimport { useIsDesktop } from './utils';\nimport { ExtendedStackNavigationOptions } from '../../layouts/StackClient';\n\nfunction ModalStackRouteDrawer({\n routeKey,\n options,\n renderScreen,\n onDismiss,\n themeColors,\n}: {\n routeKey: string;\n options: ExtendedStackNavigationOptions;\n renderScreen: () => React.ReactNode;\n onDismiss: () => void;\n themeColors: { card: string; background: string };\n}) {\n const [open, setOpen] = React.useState(true);\n // Determine sheet vs. modal with an SSR-safe hook. The first render (during\n // hydration) always assumes mobile/sheet to match the server markup; an\n // effect then updates the state after mount if the viewport is desktop.\n const isDesktop = useIsDesktop();\n const isSheet = !isDesktop;\n\n // Resolve snap points logic.\n const allowed = options.sheetAllowedDetents;\n\n const isArrayDetents = Array.isArray(allowed);\n const useCustomSnapPoints = isArrayDetents && !(allowed.length === 1 && allowed[0] === 1);\n\n let snapPoints: (number | string)[] | undefined = useCustomSnapPoints\n ? (allowed as (number | string)[])\n : undefined;\n\n if (!isSheet) {\n snapPoints = [1];\n }\n\n const [snap, setSnap] = React.useState<number | string | null>(\n useCustomSnapPoints && isArrayDetents ? allowed[0] : 1\n );\n\n // Update the snap value when custom snap points change.\n React.useEffect(() => {\n if (isSheet) {\n const next = useCustomSnapPoints && isArrayDetents ? allowed[0] : 1;\n setSnap(next);\n } else {\n // Desktop modal always fixed snap at 1\n setSnap(1);\n }\n }, [isSheet, useCustomSnapPoints, isArrayDetents, allowed]);\n\n // Map react-native-screens ios sheet undimmed logic to Vaul's fadeFromIndex\n const fadeFromIndex = isSheet\n ? options.sheetLargestUndimmedDetentIndex === 'last'\n ? (snapPoints?.length ?? 0)\n : typeof options.sheetLargestUndimmedDetentIndex === 'number'\n ? options.sheetLargestUndimmedDetentIndex + 1\n : 0\n : 0;\n\n // --- Styling -----------------------------------------------------------\n\n // Using CSS variables so defaults live in CSS and can be overridden via props.\n const modalStyleVars: CSSWithVars = {\n backgroundColor: themeColors.background,\n };\n\n if (!isSheet) {\n if (options.webModalStyle?.width) {\n modalStyleVars['--expo-router-modal-width'] =\n typeof options.webModalStyle.width === 'number'\n ? `${options.webModalStyle.width}px`\n : options.webModalStyle.width;\n\n modalStyleVars['--expo-router-modal-max-width'] =\n typeof options.webModalStyle.width === 'number'\n ? `${options.webModalStyle.width}px`\n : options.webModalStyle.width;\n\n // Also set explicit width so browsers that ignore CSS vars in `width` prop still work.\n modalStyleVars.width =\n typeof options.webModalStyle.width === 'number'\n ? `${options.webModalStyle.width}px`\n : options.webModalStyle.width;\n }\n\n // Min width override\n if (options.webModalStyle?.minWidth) {\n const mw =\n typeof options.webModalStyle.minWidth === 'number'\n ? `${options.webModalStyle.minWidth}px`\n : options.webModalStyle.minWidth;\n modalStyleVars['--expo-router-modal-min-width'] = mw;\n modalStyleVars.minWidth = mw;\n }\n\n if (options.webModalStyle?.height) {\n const h =\n typeof options.webModalStyle.height === 'number'\n ? `${options.webModalStyle.height}px`\n : options.webModalStyle.height;\n modalStyleVars['--expo-router-modal-height'] = h;\n modalStyleVars.maxHeight = h;\n modalStyleVars.height = h;\n modalStyleVars.minHeight = h;\n }\n\n // Separate min-height override (takes precedence over modalHeight)\n if (options.webModalStyle?.minHeight) {\n const mh =\n typeof options.webModalStyle.minHeight === 'number'\n ? `${options.webModalStyle.minHeight}px`\n : options.webModalStyle.minHeight;\n modalStyleVars['--expo-router-modal-min-height'] = mh;\n modalStyleVars.minHeight = mh;\n }\n }\n\n const fitToContents = isSheet && options.sheetAllowedDetents === 'fitToContents';\n\n if (fitToContents) {\n modalStyleVars.height = 'auto';\n modalStyleVars.minHeight = 'auto';\n // Allow sheet to grow with content but never exceed viewport height\n modalStyleVars.maxHeight = 'calc(100vh)';\n }\n\n // Apply corner radius (default 10px)\n const radiusValue = options.sheetCornerRadius ?? 10;\n const radiusCss = typeof radiusValue === 'number' ? `${radiusValue}px` : radiusValue;\n\n if (options.webModalStyle?.border) {\n modalStyleVars['--expo-router-modal-border'] = options.webModalStyle.border;\n }\n\n if (isSheet) {\n // Only top corners for mobile sheet\n modalStyleVars.borderTopLeftRadius = radiusCss;\n modalStyleVars.borderTopRightRadius = radiusCss;\n\n // Only apply CSS var override if a custom corner radius was provided\n if (options.sheetCornerRadius) {\n modalStyleVars['--expo-router-modal-border-radius'] = radiusCss;\n }\n } else {\n // All corners for desktop modal\n if (options.sheetCornerRadius) {\n modalStyleVars.borderRadius = radiusCss;\n modalStyleVars['--expo-router-modal-border-radius'] = radiusCss;\n }\n }\n // --- End Styling -----------------------------------------------------------\n\n const handleOpenChange = (open: boolean) => {\n if (!open) onDismiss();\n };\n\n // Props that only make sense for sheets\n const sheetProps = isSheet\n ? {\n snapPoints: snapPoints as (number | string)[],\n activeSnapPoint: snap,\n setActiveSnapPoint: setSnap,\n fadeFromIndex,\n }\n : {};\n\n return (\n <Drawer.Root\n key={`${routeKey}-${isSheet ? 'sheet' : 'modal'}`}\n open={open}\n dismissible={options.gestureEnabled ?? true}\n onAnimationEnd={handleOpenChange}\n shouldScaleBackground\n autoFocus\n onOpenChange={setOpen}\n {...sheetProps}>\n <Drawer.Portal>\n <Drawer.Overlay\n className={modalStyles.overlay}\n style={\n options.webModalStyle?.overlayBackground\n ? ({\n '--expo-router-modal-overlay-background': options.webModalStyle.overlayBackground,\n } as React.CSSProperties)\n : undefined\n }\n />\n <Drawer.Content\n aria-describedby=\"modal-description\"\n className={modalStyles.drawerContent}\n style={{\n pointerEvents: 'none',\n ...(fitToContents ? { height: 'auto' } : null),\n }}>\n <div\n className={modalStyles.modal}\n data-presentation={isSheet ? 'formSheet' : 'modal'}\n style={modalStyleVars}>\n {/* TODO:(@Hirbod) Figure out how to add title and description to the modal for screen readers in a meaningful way */}\n <Drawer.Title about=\"\" aria-describedby=\"\" className={modalStyles.srOnly} />\n <Drawer.Description about=\"\" className={modalStyles.srOnly} />\n {/* Render the screen content */}\n <div className={modalStyles.modalBody}>{renderScreen()}</div>\n </div>\n </Drawer.Content>\n </Drawer.Portal>\n </Drawer.Root>\n );\n}\n\nexport { ModalStackRouteDrawer };\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ExtendedStackNavigationOptions } from '../../layouts/StackClient';
|
|
3
|
+
declare function TransparentModalStackRouteDrawer({ routeKey, options, renderScreen, onDismiss, }: {
|
|
4
|
+
routeKey: string;
|
|
5
|
+
options: ExtendedStackNavigationOptions;
|
|
6
|
+
renderScreen: () => React.ReactNode;
|
|
7
|
+
onDismiss: () => void;
|
|
8
|
+
}): React.JSX.Element;
|
|
9
|
+
export { TransparentModalStackRouteDrawer };
|
|
10
|
+
//# sourceMappingURL=TransparentModalStackRouteDrawer.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransparentModalStackRouteDrawer.web.d.ts","sourceRoot":"","sources":["../../../src/modal/web/TransparentModalStackRouteDrawer.web.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAE3E,iBAAS,gCAAgC,CAAC,EACxC,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,8BAA8B,CAAC;IACxC,YAAY,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,qBAuBA;AAED,OAAO,EAAE,gCAAgC,EAAE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
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.TransparentModalStackRouteDrawer = TransparentModalStackRouteDrawer;
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const vaul_1 = require("vaul");
|
|
10
|
+
const modalStyles_1 = __importDefault(require("./modalStyles"));
|
|
11
|
+
function TransparentModalStackRouteDrawer({ routeKey, options, renderScreen, onDismiss, }) {
|
|
12
|
+
const handleOpenChange = (open) => {
|
|
13
|
+
if (!open)
|
|
14
|
+
onDismiss();
|
|
15
|
+
};
|
|
16
|
+
return (<vaul_1.Drawer.Root defaultOpen autoFocus key={`${routeKey}-transparent`} dismissible={options.gestureEnabled ?? false} onAnimationEnd={handleOpenChange}>
|
|
17
|
+
<vaul_1.Drawer.Portal>
|
|
18
|
+
<vaul_1.Drawer.Content className={modalStyles_1.default.transparentDrawerContent}>
|
|
19
|
+
{/* TODO:(@Hirbod) Figure out how to add title and description to the modal for screen readers in a meaningful way */}
|
|
20
|
+
<vaul_1.Drawer.Title about="" aria-describedby="" className={modalStyles_1.default.srOnly}/>
|
|
21
|
+
<vaul_1.Drawer.Description about="" className={modalStyles_1.default.srOnly}/>
|
|
22
|
+
{/* Render the screen content */}
|
|
23
|
+
<div className={modalStyles_1.default.modalBody}>{renderScreen()}</div>
|
|
24
|
+
</vaul_1.Drawer.Content>
|
|
25
|
+
</vaul_1.Drawer.Portal>
|
|
26
|
+
</vaul_1.Drawer.Root>);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=TransparentModalStackRouteDrawer.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransparentModalStackRouteDrawer.web.js","sourceRoot":"","sources":["../../../src/modal/web/TransparentModalStackRouteDrawer.web.tsx"],"names":[],"mappings":";AAAA,YAAY,CAAC;;;;;AA0CJ,4EAAgC;AAzCzC,kDAA0B;AAC1B,+BAA8B;AAE9B,gEAAwC;AAGxC,SAAS,gCAAgC,CAAC,EACxC,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GAMV;IACC,MAAM,gBAAgB,GAAG,CAAC,IAAa,EAAE,EAAE;QACzC,IAAI,CAAC,IAAI;YAAE,SAAS,EAAE,CAAC;IACzB,CAAC,CAAC;IAEF,OAAO,CACL,CAAC,aAAM,CAAC,IAAI,CACV,WAAW,CACX,SAAS,CACT,GAAG,CAAC,CAAC,GAAG,QAAQ,cAAc,CAAC,CAC/B,WAAW,CAAC,CAAC,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC,CAC7C,cAAc,CAAC,CAAC,gBAAgB,CAAC,CACjC;MAAA,CAAC,aAAM,CAAC,MAAM,CACZ;QAAA,CAAC,aAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,qBAAW,CAAC,wBAAwB,CAAC,CAC9D;UAAA,CAAC,oHAAoH,CACrH;UAAA,CAAC,aAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,qBAAW,CAAC,MAAM,CAAC,EACzE;UAAA,CAAC,aAAM,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,qBAAW,CAAC,MAAM,CAAC,EAC3D;UAAA,CAAC,+BAA+B,CAChC;UAAA,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,qBAAW,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,EAAE,GAAG,CAC9D;QAAA,EAAE,aAAM,CAAC,OAAO,CAClB;MAAA,EAAE,aAAM,CAAC,MAAM,CACjB;IAAA,EAAE,aAAM,CAAC,IAAI,CAAC,CACf,CAAC;AACJ,CAAC","sourcesContent":["'use client';\nimport React from 'react';\nimport { Drawer } from 'vaul';\n\nimport modalStyles from './modalStyles';\nimport { ExtendedStackNavigationOptions } from '../../layouts/StackClient';\n\nfunction TransparentModalStackRouteDrawer({\n routeKey,\n options,\n renderScreen,\n onDismiss,\n}: {\n routeKey: string;\n options: ExtendedStackNavigationOptions;\n renderScreen: () => React.ReactNode;\n onDismiss: () => void;\n}) {\n const handleOpenChange = (open: boolean) => {\n if (!open) onDismiss();\n };\n\n return (\n <Drawer.Root\n defaultOpen\n autoFocus\n key={`${routeKey}-transparent`}\n dismissible={options.gestureEnabled ?? false}\n onAnimationEnd={handleOpenChange}>\n <Drawer.Portal>\n <Drawer.Content className={modalStyles.transparentDrawerContent}>\n {/* TODO:(@Hirbod) Figure out how to add title and description to the modal for screen readers in a meaningful way */}\n <Drawer.Title about=\"\" aria-describedby=\"\" className={modalStyles.srOnly} />\n <Drawer.Description about=\"\" className={modalStyles.srOnly} />\n {/* Render the screen content */}\n <div className={modalStyles.modalBody}>{renderScreen()}</div>\n </Drawer.Content>\n </Drawer.Portal>\n </Drawer.Root>\n );\n}\n\nexport { TransparentModalStackRouteDrawer };\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modalStyles.d.ts","sourceRoot":"","sources":["../../../src/modal/web/modalStyles.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,kCAAkC,CAAC;AAEtD,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const modal_module_css_1 = __importDefault(require("../../../assets/modal.module.css"));
|
|
7
|
+
exports.default = modal_module_css_1.default;
|
|
8
|
+
//# sourceMappingURL=modalStyles.js.map
|
|
@@ -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
|
|