one
Version:
One is a new React Framework that makes Vite serve both native and web.
239 lines (236 loc) • 10.3 kB
JavaScript
import { createNavigatorFactory } from "@react-navigation/core";
import { SafeAreaProviderCompat } from "@react-navigation/elements";
import React, { memo, Suspense } from "react";
import { SafeAreaView, ScrollView, View, TouchableOpacity, Text } from "react-native-web";
import { ServerContextScript } from "../server/ServerContextScript";
import { getPageExport } from "../utils/getPageExport";
import { EmptyRoute } from "../views/EmptyRoute";
import { Try } from "../views/Try";
import { DevHead } from "../vite/DevHead";
import { useServerContext } from "../vite/one-server-only";
import { filterRootHTML } from "./filterRootHTML";
import {
Route,
useRouteNode
} from "./Route";
import { sortRoutesWithInitial } from "./sortRoutes";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { createElement } from "react";
const { Screen, Group } = createNavigatorFactory({})();
function getSortedChildren(children, order, initialRouteName, options) {
if (!order?.length)
return children.sort(sortRoutesWithInitial(initialRouteName)).map((route) => ({ route, props: {} }));
const entries = [...children], ordered = order.map(({ name, redirect, initialParams, listeners, options: options2, getId }) => {
if (!entries.length)
return console.warn(`[Layout children]: Too many screens defined. Route "${name}" is extraneous.`), null;
const matchIndex = entries.findIndex((child) => child.route === name);
if (matchIndex === -1)
return console.warn(
`[Layout children]: No route named "${name}" exists in nested children:`,
children.map(({ route }) => route)
), null;
const match = entries[matchIndex];
if (entries.splice(matchIndex, 1), redirect) {
if (typeof redirect == "string")
throw new Error("Redirecting to a specific route is not supported yet.");
return null;
}
return {
route: match,
props: { initialParams, listeners, options: options2, getId }
};
}).filter(Boolean);
return options?.onlyMatching || ordered.push(
...entries.sort(sortRoutesWithInitial(initialRouteName)).map((route) => ({ route, props: {} }))
), ordered;
}
function useSortedScreens(order, options) {
const node = useRouteNode();
return React.useMemo(() => (node?.children?.length ? getSortedChildren(node.children, order, node.initialRouteName, options) : []).map((value) => routeToScreen(value.route, value.props)), [node?.children, node?.initialRouteName, order]);
}
function fromImport({ ErrorBoundary, ...component }) {
if (ErrorBoundary)
return {
default: React.forwardRef((props, ref) => {
const children = React.createElement(getPageExport(component) || EmptyRoute, {
...props,
ref
});
return /* @__PURE__ */ jsx(Try, { catch: ErrorBoundary, children });
})
};
if (process.env.NODE_ENV !== "production") {
const exported = getPageExport(component);
if (exported && typeof exported == "object" && Object.keys(exported).length === 0)
return { default: EmptyRoute };
}
return { default: getPageExport(component) };
}
const qualifiedStore = /* @__PURE__ */ new WeakMap();
function getQualifiedRouteComponent(value) {
if (value && qualifiedStore.has(value))
return qualifiedStore.get(value);
const ScreenComponent = React.forwardRef((props, ref) => {
const res = value.loadRoute(), Component = getPageExport(fromImport(res));
if (process.env.NODE_ENV === "development" && process.env.DEBUG === "one" && (console.groupCollapsed(`Render ${props.key} ${props.segment}`), console.info("value", value), console.info("Component", Component), console.groupEnd()), props.segment === "") {
const out = Component(props, ref), { children, bodyProps, head, htmlProps } = filterRootHTML(out), { children: headChildren, ...headProps } = head?.props || {}, serverContext = useServerContext();
let finalChildren = children;
return finalChildren = /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("head", { ...headProps, children: [
/* @__PURE__ */ jsx(DevHead, {}),
/* @__PURE__ */ jsx(
"script",
{
dangerouslySetInnerHTML: {
__html: "globalThis['global'] = globalThis"
}
}
),
serverContext?.css?.map((file) => /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: file }, file)),
/* @__PURE__ */ jsx(ServerContextScript, {}),
headChildren
] }, "head"),
/* @__PURE__ */ jsx("body", { suppressHydrationWarning: !0, ...bodyProps, children: /* @__PURE__ */ jsx(SafeAreaProviderCompat, { children: finalChildren }) }, "body")
] }), // tamagui and libraries can add className on hydration to have ssr safe styling
// so supress hydration warnings here
/* @__PURE__ */ jsx("html", { suppressHydrationWarning: !0, lang: "en-US", ...htmlProps, children: finalChildren });
}
return /* @__PURE__ */ jsx(RouteErrorBoundary, { routeName: value.route, children: /* @__PURE__ */ jsx(Component, { ...props, ref }) });
}), wrapSuspense = (children) => process.env.ONE_SUSPEND_ROUTES === "1" ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children }) : children, QualifiedRoute = React.forwardRef(
({
// Remove these React Navigation props to
// enforce usage of router hooks (where the query params are correct).
route,
navigation,
// Pass all other props to the component
...props
}, ref) => /* @__PURE__ */ jsx(Route, { route, node: value, children: /* @__PURE__ */ jsx(Fragment, { children: wrapSuspense(
/* @__PURE__ */ jsx(
ScreenComponent,
{
...props,
ref,
// Expose the template segment path, e.g. `(home)`, `[foo]`, `index`
// the intention is to make it possible to deduce shared routes.
segment: value.route
}
)
) }) })
);
return QualifiedRoute.displayName = `Route(${value.route})`, qualifiedStore.set(value, QualifiedRoute), memo(QualifiedRoute);
}
function createGetIdForRoute(route) {
const include = /* @__PURE__ */ new Map();
if (route.dynamic)
for (const segment of route.dynamic)
include.set(segment.name, segment);
return ({ params = {} } = {}) => {
const segments = [];
for (const dynamic of include.values()) {
const value = params?.[dynamic.name];
Array.isArray(value) && value.length > 0 ? segments.push(value.join("/")) : value && !Array.isArray(value) ? segments.push(value) : dynamic.deep ? segments.push(`[...${dynamic.name}]`) : segments.push(`[${dynamic.name}]`);
}
return segments.join("/") ?? route.contextKey;
};
}
function routeToScreen(route, { options, ...props } = {}) {
return /* @__PURE__ */ createElement(
Screen,
{
getId: createGetIdForRoute(route),
...props,
name: route.route,
key: route.route,
options: (args) => {
const staticOptions = route.generated ? route.loadRoute()?.getNavOptions : null, staticResult = typeof staticOptions == "function" ? staticOptions(args) : staticOptions, dynamicResult = typeof options == "function" ? options?.(args) : options, output = {
...staticResult,
...dynamicResult
};
return route.generated && (output.tabBarButton = () => null, output.drawerItemStyle = { height: 0, display: "none" }), output;
},
getComponent: () => getQualifiedRouteComponent(route)
}
);
}
const ROUTE_ERROR_BOUNDARY_INITIAL_STATE = {
hasError: !1,
error: null,
errorInfo: null
};
class RouteErrorBoundary extends React.Component {
constructor(props) {
super(props), this.state = ROUTE_ERROR_BOUNDARY_INITIAL_STATE;
}
static getDerivedStateFromError(error) {
return { hasError: !0, error };
}
componentDidCatch(error, errorInfo) {
console.error(
`Error occurred while running route "${this.props.routeName}": ${error instanceof Error ? error.message : error}
${error.stack}
Component Stack:
${errorInfo.componentStack}`
), this.setState({ errorInfo });
}
clearError() {
this.setState(ROUTE_ERROR_BOUNDARY_INITIAL_STATE);
}
render() {
if (this.state.hasError) {
const { error, errorInfo } = this.state;
return /* @__PURE__ */ jsx(SafeAreaView, { style: { backgroundColor: "#000" }, children: /* @__PURE__ */ jsxs(View, { style: { margin: 16, gap: 16 }, children: [
/* @__PURE__ */ jsxs(
Text,
{
style: {
alignSelf: "flex-start",
padding: 5,
margin: -5,
backgroundColor: "red",
color: "white",
fontSize: 20,
fontFamily: "monospace"
},
children: [
'Error on route "',
this.props.routeName,
'"'
]
}
),
/* @__PURE__ */ jsx(Text, { style: { color: "white", fontSize: 16, fontFamily: "monospace" }, children: error instanceof Error ? error.message : error }),
/* @__PURE__ */ jsx(TouchableOpacity, { onPress: this.clearError.bind(this), children: /* @__PURE__ */ jsx(
Text,
{
style: {
alignSelf: "flex-start",
margin: -6,
padding: 6,
backgroundColor: "white",
color: "black",
fontSize: 14,
fontFamily: "monospace"
},
children: "Retry"
}
) }),
/* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: { gap: 12 }, children: [
error instanceof Error ? /* @__PURE__ */ jsx(Text, { style: { color: "white", fontSize: 12, fontFamily: "monospace" }, children: error.stack }) : null,
errorInfo?.componentStack ? /* @__PURE__ */ jsxs(Text, { style: { color: "white", fontSize: 12, fontFamily: "monospace" }, children: [
"Component Stack: ",
errorInfo.componentStack
] }) : null
] })
] }) });
}
return this.props.children;
}
}
export {
Group,
Screen,
createGetIdForRoute,
getQualifiedRouteComponent,
useSortedScreens
};
//# sourceMappingURL=useScreens.js.map