UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

239 lines (236 loc) 10.3 kB
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