@native-router/react 1.3.1 → 1.5.0
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/README.md +86 -4
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.js +1 -1
- package/dist/ssr-873l9jA-.js +8 -0
- package/dist/ssr-873l9jA-.js.map +1 -0
- package/dist/ssr-C-Bgsx5W.cjs +2 -0
- package/dist/ssr-C-Bgsx5W.cjs.map +1 -0
- package/dist/types/components/Link.d.ts +8 -7
- package/dist/types/components/NavLink.d.ts +8 -22
- package/dist/types/components/PrefetchLink.d.ts +8 -16
- package/dist/types/components/RouteErrorBoundary.d.ts +31 -0
- package/dist/types/components/TypedLink.d.ts +8 -0
- package/dist/types/create-routes.d.ts +27 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/types.d.ts +146 -3
- package/dist/types/use-blocker.d.ts +25 -0
- package/dist/types/use-search-params.d.ts +27 -0
- package/package.json +6 -6
- package/dist/ssr-Cow2_ikA.cjs +0 -2
- package/dist/ssr-Cow2_ikA.cjs.map +0 -1
- package/dist/ssr-j5c5cH-B.js +0 -8
- package/dist/ssr-j5c5cH-B.js.map +0 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{Component as r,createContext as e,useCallback as n,useContext as t,useEffect as o,useMemo as i,useRef as a,useState as u}from"react";import{createBrowserHistory as c,createHashHistory as s,createMemoryHistory as l,createPath as d}from"history";import{Fragment as h,jsx as f,jsxs as v}from"react/jsx-runtime";import{create as m,getCurrentView as p,listen as g,match as y,mergeMatchedParams as w,parseSearch as x,parseSearchInput as P,resolve as b,setOptions as C,toLocation as A}from"@native-router/core";import{isString as E,splitProps as R,uniqId as S}from"@native-router/core/util";import{useSyncExternalStore as k}from"use-sync-external-store/shim";var K=e(null);function $(r){/* @__PURE__ */
|
|
2
|
+
return f(K.Provider,{...r})}function H(){return t(K)}var I=e(null);function L(){const r=H(),e=t(I);return r??e}var V=e([void 0,{}]);function _(){return t(V)}function D(){return _()[1]}function M({children:r,name:e,data:n}){const t=D(),o=i(()=>[n,e?{...t,[e]:n}:t],[n,e,t]);/* @__PURE__ */
|
|
3
|
+
return f(V.Provider,{value:o,children:r})}var U=e(void 0);function W(){return t(U)}function j(r){const[e,n]=_();return r?n[r]:e}var F=e(void 0);function J(){return t(F)}var N=class extends r{state={};static getDerivedStateFromError(r){return{error:r}}render(){const{error:r}=this.state;if(!r)return this.props.children;const{route:e,ctx:n,router:t}=this.props,o=e.errorComponent;if(o)/* @__PURE__ */return f(o,{error:r,ctx:{...n,phase:"render"}});const i=t.errorHandler?.(r);if(i instanceof Promise)throw i.catch(()=>{}),r;if(void 0!==i)return i;throw r}};function O(r,e){return z(r,e,(r,e)=>r?.(e))}var T=/* @__PURE__ */new WeakMap;function q(r,e){const n=new Array(r.length);return z(r,e,(r,e)=>Promise.resolve(r?.(e)).then(r=>n[e.index]=r)).then(r=>(T.set(r,n),r))}function z(r,{router:e,location:n,signal:t},o){return Promise.all(r.map(({route:i},a)=>{const u={matched:r,params:w(r,a),index:a,router:e,location:n,search:P(n.search),signal:t??(new AbortController).signal};return Promise.all([i.search?x(i.search,n.search).then(r=>(u.search=r,o(i.data,u))):o(i.data,u),function(){if(!i.component)return L;const r=i.component(u);return Promise.resolve(r).then(r=>"default"in r?r.default:r)}()]).then(([r,n])=>/* @__PURE__ */f(N,{route:i,ctx:u,router:e,children:/* @__PURE__ */f(M,{data:r,name:i.name,children:/* @__PURE__ */f(U.Provider,{value:u,children:/* @__PURE__ */f(n,{})})})},`${a}:${i.path??""}`),r=>{if(!i.errorComponent)throw r;/* @__PURE__ */
|
|
4
|
+
return f(M,{data:void 0,name:i.name,children:/* @__PURE__ */f(U.Provider,{value:u,children:/* @__PURE__ */f(i.errorComponent,{error:r,ctx:u})})})})})).then(r=>r.reverse().reduce((r,e)=>/* @__PURE__ */f($,{value:r,children:e})))}var B=e(null);function G({router:r,children:e}){const o=a(p(r)),i=n(e=>g(r,r=>{o.current=r,e()}),[r]),u=n(()=>o.current,[]),c=n(()=>p(r),[r]),s=k(i,u,c),l=t(F),d=null==s&&"pending"===l?.status?function(r,e){const{resolving:n}=r,t=n?y(r,n.pathname):void 0;if(!t)return null;for(let o=t.length-1;o>=0;o--){const r=t[o].route.pendingComponent;if(r)/* @__PURE__ */return f(r,{},e)}return null}(r,l.key):null;/* @__PURE__ */
|
|
5
|
+
return f(B.Provider,{value:r,children:void 0===e?s??d:/* @__PURE__ */f($,{value:s,children:/* @__PURE__ */f(I.Provider,{value:d,children:e})})})}function Q(r,e,{resolveView:n=O,...t}={}){return m(r,e,n,t)}function X({routes:r,children:e,...n},t){const[a,c]=R(n,["baseUrl","currentView"]),{baseUrl:s,currentView:l}=a,[d,h]=u(),v=i(()=>Q(r,t(),{...n,onLoadingChange(r){h(r&&{key:S(),status:r})}}),[r,t,s,l]);o(()=>{C(v,{...c,onLoadingChange(r){h(r&&{key:S(),status:r})}})},[v,c]);const m=i(()=>/* @__PURE__ */f(G,{router:v,children:e}),[v,e]);/* @__PURE__ */
|
|
6
|
+
return f(F.Provider,{value:d,children:m})}function Y(r){return X(r,c)}function Z(r){return X(r,s)}function rr({initialEntries:r,initialIndex:e,...n}){return X(n,i(()=>()=>l({initialEntries:r,initialIndex:e}),[r,e]))}function er(){const r=t(B);if(!r)throw new Error("useRouter() must be used within a <Router> component");return r}var nr="_nativeRouterReactSSRData";function tr(r,e,n){return b(r,e).then(t=>{const o=function(r){return T.get(r)}(t),i=r.history.location.state?.index||0;/* @__PURE__ */
|
|
7
|
+
return v(h,{children:[/* @__PURE__ */f(G,{router:r,children:t}),/* @__PURE__ */f("script",{...n?.scriptAttributes,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`window.${n?.hydrateKey||nr} = ${a={data:o,location:e,index:i},JSON.stringify(a).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")};`}})]});var a})}function or(r,e,{scriptAttributes:n,hydrateKey:t,...o}={}){const i=m(r,l({initialEntries:[e]}),q,o);return tr(i,E(e)?A(i,e):e,{scriptAttributes:n,hydrateKey:t})}function ir(r,e){const{data:n,location:t,index:o=0}=window[e?.hydrateKey||nr],i=c();i.replace(d(i.location),{index:o});const a=m(r,i,function(r){return(e,n)=>z(e,n,(e,n)=>r[n.index])}(n),e);return b(a,t).then(r=>({view:r,router:a}))}export{rr as a,er as c,j as d,J as f,H as h,Y as i,O as l,D as m,or as n,G as o,W as p,Z as r,Q as s,ir as t,L as u};
|
|
8
|
+
//# sourceMappingURL=ssr-873l9jA-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-873l9jA-.js","names":[],"sources":["../src/context.tsx","../src/components/RouteErrorBoundary.tsx","../src/resolve-view.tsx","../src/components/Router.tsx","../src/ssr.tsx"],"sourcesContent":["import {createContext, ReactNode, useContext, useMemo} from 'react';\nimport type {Context, LoadStatus, Route} from './types';\n\nconst ViewContext = createContext<ReactNode>(null);\n\nexport function ViewProvider(props: {children: ReactNode; value: ReactNode}) {\n return <ViewContext.Provider {...props} />;\n}\n\n/**\n * @group Hooks\n * @see {@link View View Component}\n */\nexport function useView() {\n return useContext(ViewContext);\n}\n\n/**\n * Route-level pending skeleton(`pendingComponent` of the nearest matched\n * ancestor), set by the Router only while a navigation is pending with no\n * previous view to retain(cold start, refresh, re-navigation after an\n * error); `null` otherwise, so in-app navigation keeps the previous view.\n * @see {@link Route.pendingComponent}\n */\nexport const PendingContext = createContext<ReactNode>(null);\n\n/**\n * Used for route component to render child route component.\n * It just render the return of {@link useView}, falling back to the\n * route-level pending skeleton when the view slot is empty.\n * @group Components\n */\nexport function View() {\n const view = useView();\n const pending = useContext(PendingContext);\n return view ?? pending;\n}\n\nconst DataContext = createContext<[any, Record<string, any>]>([undefined, {}]);\n\nfunction useDataContext() {\n return useContext(DataContext);\n}\n\n/**\n * Get the named data map of the resolved route levels: an object keyed by\n * each ancestor's `name`, holding its resolved `data`. The current level\n * is included only when it declares a `name`.\n *\n * Give the generic the expected map shape to read values type-safely:\n * `useNamedData<{user: User}>()`.\n * @group Hooks\n */\nexport function useNamedData<T = Record<string, unknown>>() {\n return useDataContext()[1] as T;\n}\n\nexport function DataProvider({\n children,\n name,\n data\n}: {\n children: ReactNode;\n data: any;\n name?: string;\n}) {\n const namedData = useNamedData();\n const value = useMemo(\n () => [data, name ? {...namedData, [name]: data} : namedData] as [any, any],\n [data, name, namedData]\n );\n return <DataContext.Provider value={value}>{children}</DataContext.Provider>;\n}\n\nexport const MatchedContext = createContext<Context<Route> | undefined>(\n undefined\n);\n\n/**\n * @group Hooks\n */\nexport function useMatched() {\n return useContext(MatchedContext)!;\n}\n\n/**\n * Get the resolved `data` of the current route level, or the named data\n * of an ancestor level when `name` is given.\n *\n * Give the generic the expected data type to read it type-safely without\n * a cast: `useData<Article>()` → `Article | undefined`.\n * @group Hooks\n */\nexport function useData<T = unknown>(name?: string): T | undefined {\n const [data, namedData] = useDataContext();\n return (name ? namedData[name] : data) as T | undefined;\n}\n\nexport const LoadingContext = createContext<LoadStatus | undefined>(undefined);\n\n/**\n * @group Hooks\n */\nexport function useLoading() {\n return useContext(LoadingContext);\n}\n","import {Component, type ReactNode} from 'react';\nimport type {Context, Route} from '@@/types';\nimport type {RouterInstance} from '@native-router/core';\n\ntype Props = {\n route: Route;\n ctx: Context<Route>;\n router: RouterInstance<Route, ReactNode>;\n children: ReactNode;\n};\n\ntype State = {error?: Error};\n\n/**\n * Route-level render error boundary: catches errors thrown while the\n * level's component subtree renders and shows the route's\n * `errorComponent` with `ctx.phase === 'render'` — the render-phase\n * twin of the resolve-phase fallback in resolve-view. Just like the\n * browser renders an error page for any failed load, no rendering error\n * of a resolved view should crash past its route.\n *\n * Without a route `errorComponent` the error goes to the global\n * `errorHandler`: a returned view renders in place, while the default\n * handler(plain rejection) and async fallbacks rethrow, resurfacing the\n * error up the React tree like any unhandled error.\n */\nexport default class RouteErrorBoundary extends Component<Props, State> {\n state: State = {};\n\n static getDerivedStateFromError(error: Error): State {\n return {error};\n }\n\n render() {\n const {error} = this.state;\n if (!error) return this.props.children;\n const {route, ctx, router} = this.props;\n const ErrorComponent = route.errorComponent;\n if (ErrorComponent) {\n return <ErrorComponent error={error} ctx={{...ctx, phase: 'render'}} />;\n }\n // No route-level fallback: hand the error to the global errorHandler.\n // An async fallback cannot render synchronously; observe its rejection\n // and resurface the error itself instead.\n const fallback = router.errorHandler?.(error);\n if (fallback instanceof Promise) {\n fallback.catch(() => undefined);\n throw error;\n }\n if (fallback !== undefined) return fallback;\n throw error;\n }\n}\n","import type {ComponentType, ReactElement} from 'react';\nimport type {Context, ResolveViewContext, Route} from '@@/types';\nimport {\n mergeMatchedParams,\n parseSearch,\n parseSearchInput\n} from '@native-router/core';\nimport type {Matched} from '@native-router/core';\nimport {DataProvider, MatchedContext, View, ViewProvider} from './context';\nimport RouteErrorBoundary from './components/RouteErrorBoundary';\n\n/**\n * The default implementation of resolve view\n * @param matched the matched result\n * @param viewContext resolved view context\n * @returns the resolve view\n * @see {@link create router->create}\n */\nexport default function resolveView(\n matched: Matched<Route>[],\n ctx: ResolveViewContext<Route>\n) {\n return resolveViewBase(matched, ctx, (data, dataCtx) => data?.(dataCtx));\n}\n\nconst viewDataMap = new WeakMap<ReactElement, any[]>();\n\nexport function resolveViewServer(\n matched: Matched<Route>[],\n ctx: ResolveViewContext<Route>\n) {\n const dataResults = new Array(matched.length);\n return resolveViewBase(matched, ctx, (data, dataCtx) =>\n Promise.resolve(data?.(dataCtx)).then(\n (result) => (dataResults[dataCtx.index] = result)\n )\n ).then((view) => {\n viewDataMap.set(view, dataResults);\n return view;\n });\n}\n\nexport function createHydrateResolveView(data: any[]) {\n return (matched: Matched<Route>[], ctx: ResolveViewContext<Route>) =>\n resolveViewBase(matched, ctx, (_, dataCtx) => data[dataCtx.index]);\n}\n\nexport function getViewData(view: ReactElement) {\n return viewDataMap.get(view);\n}\n\nfunction resolveViewBase(\n matched: Matched<Route>[],\n {router, location, signal}: ResolveViewContext<Route>,\n resolveData: (\n dataFetcher: ((ctx: Context<Route>) => any) | undefined,\n ctx: Context<Route>\n ) => any\n) {\n return Promise.all(\n matched.map(({route}, index) => {\n // `search` starts as the degraded input and is upgraded to the\n // schema output before the data fetcher runs below; schema outputs\n // are user-typed(`Route<P, S>`), so the property stays `any` here.\n const ctx: Context<Route, Record<string, string>, any> = {\n matched: matched!,\n params: mergeMatchedParams(matched, index),\n index,\n router,\n location,\n search: parseSearchInput(location.search),\n // The chain's abort signal(navigation-superseded/cancelled) is\n // forwarded to every level's loader; a hand-rolled resolveView\n // context without one still yields a never-aborting signal.\n signal: signal ?? new AbortController().signal\n };\n function resolveComponent(): ComponentType | Promise<ComponentType> {\n if (!route.component) return View;\n const r = route.component(ctx);\n return Promise.resolve(r).then((m) => ('default' in m ? m.default : m));\n }\n\n // The level's search schema runs before its data fetcher: the parsed\n // output replaces the degraded input in `ctx.search`, and a rejected\n // validation fails the level exactly like a data error.\n const resolveDataWithSearch = () =>\n route.search\n ? parseSearch(route.search, location.search).then((search) => {\n ctx.search = search;\n return resolveData(route.data, ctx);\n })\n : resolveData(route.data, ctx);\n\n // A level that fails to resolve (search, data or component) is\n // replaced by its route-level errorComponent when configured;\n // otherwise the error bubbles up to the global errorHandler as before.\n return Promise.all([resolveDataWithSearch(), resolveComponent()]).then(\n ([data, C]) => (\n // The boundary is the render-phase twin of the resolve-phase\n // fallback below: a component that throws while rendering is\n // caught here and rendered through the same route errorComponent\n // (with ctx.phase === 'render'), instead of crashing past the\n // route to the React root.\n // Keyed by the level's path so React never reuses one route's\n // boundary fiber for another's at the same slot: a retained\n // error state would otherwise leak across routes when the\n // tree diff lands the same position(the class instance — and\n // its `state.error` — survives the prop change, and React\n // replays the cached error during the swap). The level index\n // only disambiguates same-path levels of one chain.\n\n <RouteErrorBoundary\n // eslint-disable-next-line @eslint-react/no-array-index-key -- not a list key: a per-level boundary identity (path + level)\n key={`${index}:${route.path ?? ''}`}\n route={route}\n ctx={ctx}\n router={router}\n >\n <DataProvider data={data} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <C />\n </MatchedContext.Provider>\n </DataProvider>\n </RouteErrorBoundary>\n ),\n (error: Error) => {\n if (!route.errorComponent) throw error;\n return (\n <DataProvider data={undefined} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <route.errorComponent error={error} ctx={ctx} />\n </MatchedContext.Provider>\n </DataProvider>\n );\n }\n );\n })\n ).then((views) =>\n views\n .reverse()\n .reduce((acc, view) => <ViewProvider value={acc}>{view}</ViewProvider>)\n );\n}\n","import {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {\n History,\n createBrowserHistory,\n createHashHistory,\n createMemoryHistory,\n MemoryHistoryOptions\n} from 'history';\nimport type {LoadStatus, Route} from '@@/types';\nimport {LoadingContext, PendingContext, ViewProvider} from '@@/context';\nimport {\n create,\n getCurrentView,\n listen,\n match,\n setOptions\n} from '@native-router/core';\nimport type {Options, ResolveView, RouterInstance} from '@native-router/core';\nimport {splitProps, uniqId} from '@native-router/core/util';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport defaultResolve from '@@/resolve-view';\n\nconst RouterContext = createContext<RouterInstance<Route, ReactNode> | null>(\n null\n);\n\ntype Props = {\n children?: ReactNode;\n routes: Route[] | Route;\n resolveView?: typeof defaultResolve;\n} & Omit<Options<ReactNode>, 'onLoadingChange'>;\n\n/**\n * Base Router Component.\n * @group Components\n */\nexport function Router({\n router,\n children\n}: {\n children?: ReactNode;\n router: RouterInstance<Route, ReactNode>;\n}) {\n const viewRef = useRef<ReactNode>(getCurrentView(router));\n const subscribe = useCallback(\n (onStoreChange: () => void) =>\n listen(router, (view) => {\n viewRef.current = view;\n onStoreChange();\n }),\n [router]\n );\n const getSnapshot = useCallback(() => viewRef.current, []);\n // `getServerSnapshot` is required by the native implementation when the\n // Router is rendered inside server-rendered content(e.g. resolveServerView).\n const getServerSnapshot = useCallback(() => getCurrentView(router), [router]);\n const view = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n // Route-level pending skeleton, only when no previous view is retained\n // (cold start, refresh, re-navigation after an error); in-app navigation\n // keeps the old view by design, the global loading signal already\n // covers that phase. Reading LoadingContext here also re-renders the\n // Router on every loading transition.\n const loading = useContext(LoadingContext);\n const pending =\n view == null && loading?.status === 'pending'\n ? resolvePendingView(router, loading.key)\n : null;\n\n return (\n <RouterContext.Provider value={router}>\n {children === undefined ? (\n (view ?? pending)\n ) : (\n <ViewProvider value={view}>\n <PendingContext.Provider value={pending}>\n {children}\n </PendingContext.Provider>\n </ViewProvider>\n )}\n </RouterContext.Provider>\n );\n}\n\n/**\n * Render the `pendingComponent` of the nearest matched ancestor of the\n * resolving location, walked deepest first(the resolving route's own\n * included). Keyed by the loading episode so a new pending phase remounts\n * the skeleton(stateful shimmer animations restart). Guards may still\n * redirect the resolution away; until then the initially matched chain\n * is the best — and only — answer available.\n */\nfunction resolvePendingView(\n router: RouterInstance<Route, ReactNode>,\n key: number\n) {\n const {resolving} = router;\n const matched = resolving ? match(router, resolving.pathname) : undefined;\n if (!matched) return null;\n for (let i = matched.length - 1; i >= 0; i--) {\n const Pending = matched[i].route.pendingComponent;\n if (Pending) return <Pending key={key} />;\n }\n return null;\n}\n\nexport function createRouter(\n routes: Route | Route[],\n history: History,\n {\n resolveView = defaultResolve,\n ...options\n }: Options<ReactNode> & {resolveView?: ResolveView<Route, ReactNode>} = {}\n): RouterInstance<Route, ReactNode> {\n return create(routes, history, resolveView, options);\n}\n\nfunction useNewRouter(\n {routes, children, ...options}: Props,\n createHistory: () => History\n) {\n const [tracked, rest] = splitProps(options, ['baseUrl', 'currentView']);\n const {baseUrl, currentView} = tracked;\n const [loading, setLoading] = useState<LoadStatus>();\n // Initial options are baked in at creation: the cold-start resolve fires\n // from the subscribe effect(children effects run first) before the\n // setOptions effect below runs, and the default errorHandler would let\n // listen's refresh().catch(noop) swallow a first failure, leaving the\n // view blank forever.\n const router = useMemo(\n () =>\n createRouter(routes, createHistory(), {\n ...options,\n onLoadingChange(status) {\n setLoading(status && {key: uniqId(), status});\n }\n }),\n // Only the tracked options belong to the deps: option updates flow\n // through the setOptions effect below instead of recreating the router.\n [routes, createHistory, baseUrl, currentView]\n );\n\n // Options are refreshed on every commit, so `onLoadingChange` and the\n // callback options always see the latest closure.\n useEffect(() => {\n setOptions(router, {\n ...rest,\n onLoadingChange(status) {\n setLoading(status && {key: uniqId(), status});\n }\n });\n }, [router, rest]);\n\n const r = useMemo(\n () => <Router router={router}>{children}</Router>,\n [router, children]\n );\n\n return <LoadingContext.Provider value={loading}>{r}</LoadingContext.Provider>;\n}\n\n/**\n * History mode Router Component.\n * @group Components\n */\nexport function HistoryRouter(props: Props) {\n return useNewRouter(props, createBrowserHistory);\n}\n\n/**\n * Hash mode Router Component.\n * @group Components\n */\nexport function HashRouter(props: Props) {\n return useNewRouter(props, createHashHistory);\n}\n\n/**\n * Memory mode Router Component.\n * @group Components\n */\nexport function MemoryRouter({\n initialEntries,\n initialIndex,\n ...props\n}: Props & MemoryHistoryOptions) {\n const createHistory = useMemo(\n () => () => createMemoryHistory({initialEntries, initialIndex}),\n [initialEntries, initialIndex]\n );\n return useNewRouter(props, createHistory);\n}\n\n/**\n * Get Router instance.\n * @group Hooks\n * @returns Router Instance\n */\nexport function useRouter() {\n const router = useContext(RouterContext);\n if (!router) {\n throw new Error('useRouter() must be used within a <Router> component');\n }\n return router;\n}\n","import {createBrowserHistory, createMemoryHistory, createPath} from 'history';\nimport {ReactElement, ReactNode} from 'react';\nimport {create, resolve, toLocation} from '@native-router/core';\nimport type {\n HistoryState,\n Location,\n Options,\n RouterInstance\n} from '@native-router/core';\nimport {isString} from '@native-router/core/util';\nimport {Router} from './components/Router';\nimport {\n createHydrateResolveView,\n getViewData,\n resolveViewServer\n} from './resolve-view';\nimport type {Route} from './types';\n\nconst defaultHydrateKey = '_nativeRouterReactSSRData';\n\n/**\n * Serialize the SSR payload for embedding in a script element.\n * `JSON.stringify` does not escape `<`, U+2028 and U+2029,\n * so route data like `</script>` would break out of the script element.\n * @param payload the payload to serialize\n * @returns the escaped JSON string\n */\nfunction serializePayload(payload: {\n data?: any[];\n location: Location;\n index: number;\n}) {\n return JSON.stringify(payload)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nexport function resolveServerViewBase(\n router: RouterInstance<Route, ReactNode>,\n location: Location,\n options?: {\n scriptAttributes?: Record<string, string>;\n hydrateKey?: string;\n }\n) {\n return resolve<Route, ReactNode>(router, location).then((view) => {\n const data = getViewData(view as ReactElement);\n const index =\n (router.history.location.state as HistoryState | undefined)?.index || 0;\n return (\n <>\n <Router router={router}>{view}</Router>\n <script\n {...options?.scriptAttributes}\n suppressHydrationWarning\n // eslint-disable-next-line @eslint-react/dom-no-dangerously-set-innerhtml -- serialized state for hydration\n dangerouslySetInnerHTML={{\n __html: `window.${\n options?.hydrateKey || defaultHydrateKey\n } = ${serializePayload({data, location, index})};`\n }}\n />\n </>\n );\n });\n}\n\nexport function resolveServerView(\n routes: Route | Route[],\n location: Location | string,\n {\n scriptAttributes,\n hydrateKey,\n ...options\n }: Options<ReactElement> & {\n scriptAttributes?: Record<string, string>;\n hydrateKey?: string;\n } = {}\n) {\n const router = create(\n routes,\n createMemoryHistory({initialEntries: [location]}),\n resolveViewServer,\n options\n );\n\n return resolveServerViewBase(\n router,\n isString(location) ? toLocation(router, location) : location,\n {\n scriptAttributes,\n hydrateKey\n }\n );\n}\n\n/**\n * Hydrate the SSR result of {@link resolveServerView} on the client.\n * The router is bound to the browser history(aligned with the index\n * in the SSR payload), so navigation after hydration updates the address bar.\n * @param routes routes config, must match the server side\n * @param options options, `hydrateKey` must match the server side\n * @returns the resolved view and the router instance, for example:\n * `const {view, router} = await hydrate(routes);`\n * `hydrateRoot(root, <Router router={router}>{view}</Router>)`\n * @group Methods\n */\nexport function hydrate(\n routes: Route | Route[],\n options?: Options<ReactElement> & {\n hydrateKey?: string;\n }\n): Promise<{view: ReactNode; router: RouterInstance<Route, ReactNode>}> {\n const {\n data,\n location,\n index = 0\n } = (window as any)[options?.hydrateKey || defaultHydrateKey] as {\n data: any[];\n location: Location;\n index?: number;\n };\n const history = createBrowserHistory();\n history.replace(createPath(history.location), {index});\n const router = create(\n routes,\n history,\n createHydrateResolveView(data),\n options\n );\n return resolve<Route, ReactNode>(router, location).then((view) => ({\n view,\n router\n }));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("react"),r=require("history"),t=require("react/jsx-runtime"),n=require("@native-router/core"),o=require("@native-router/core/util"),u=require("use-sync-external-store/shim");var i=(0,e.createContext)(null);function a(e){return(0,t.jsx)(i.Provider,{...e})}function s(){return(0,e.useContext)(i)}var c=(0,e.createContext)(null);function l(){const r=s(),t=(0,e.useContext)(c);return r??t}var d=(0,e.createContext)([void 0,{}]);function f(){return(0,e.useContext)(d)}function h(){return f()[1]}function p({children:r,name:n,data:o}){const u=h(),i=(0,e.useMemo)(()=>[o,n?{...u,[n]:o}:u],[o,n,u]);return(0,t.jsx)(d.Provider,{value:i,children:r})}var x=(0,e.createContext)(void 0);function m(){return(0,e.useContext)(x)}function v(e){const[r,t]=f();return e?t[e]:r}var y=(0,e.createContext)(void 0);function g(){return(0,e.useContext)(y)}var b=class extends e.Component{state={};static getDerivedStateFromError(e){return{error:e}}render(){const{error:e}=this.state;if(!e)return this.props.children;const{route:r,ctx:n,router:o}=this.props,u=r.errorComponent;if(u)return(0,t.jsx)(u,{error:e,ctx:{...n,phase:"render"}});const i=o.errorHandler?.(e);if(i instanceof Promise)throw i.catch(()=>{}),e;if(void 0!==i)return i;throw e}};function j(e,r){return w(e,r,(e,r)=>e?.(r))}var P=new WeakMap;function C(e,r){const t=new Array(e.length);return w(e,r,(e,r)=>Promise.resolve(e?.(r)).then(e=>t[r.index]=e)).then(e=>(P.set(e,t),e))}function w(e,{router:r,location:o,signal:u},i){return Promise.all(e.map(({route:a},s)=>{const c={matched:e,params:(0,n.mergeMatchedParams)(e,s),index:s,router:r,location:o,search:(0,n.parseSearchInput)(o.search),signal:u??(new AbortController).signal};return Promise.all([a.search?(0,n.parseSearch)(a.search,o.search).then(e=>(c.search=e,i(a.data,c))):i(a.data,c),function(){if(!a.component)return l;const e=a.component(c);return Promise.resolve(e).then(e=>"default"in e?e.default:e)}()]).then(([e,n])=>(0,t.jsx)(b,{route:a,ctx:c,router:r,children:(0,t.jsx)(p,{data:e,name:a.name,children:(0,t.jsx)(x.Provider,{value:c,children:(0,t.jsx)(n,{})})})},`${s}:${a.path??""}`),e=>{if(!a.errorComponent)throw e;return(0,t.jsx)(p,{data:void 0,name:a.name,children:(0,t.jsx)(x.Provider,{value:c,children:(0,t.jsx)(a.errorComponent,{error:e,ctx:c})})})})})).then(e=>e.reverse().reduce((e,r)=>(0,t.jsx)(a,{value:e,children:r})))}var O=(0,e.createContext)(null);function R({router:r,children:o}){const i=(0,e.useRef)((0,n.getCurrentView)(r)),s=(0,e.useCallback)(e=>(0,n.listen)(r,r=>{i.current=r,e()}),[r]),l=(0,e.useCallback)(()=>i.current,[]),d=(0,e.useCallback)(()=>(0,n.getCurrentView)(r),[r]),f=(0,u.useSyncExternalStore)(s,l,d),h=(0,e.useContext)(y),p=null==f&&"pending"===h?.status?function(e,r){const{resolving:o}=e,u=o?(0,n.match)(e,o.pathname):void 0;if(!u)return null;for(let n=u.length-1;n>=0;n--){const e=u[n].route.pendingComponent;if(e)return(0,t.jsx)(e,{},r)}return null}(r,h.key):null;return(0,t.jsx)(O.Provider,{value:r,children:void 0===o?f??p:(0,t.jsx)(a,{value:f,children:(0,t.jsx)(c.Provider,{value:p,children:o})})})}function S(e,r,{resolveView:t=j,...o}={}){return(0,n.create)(e,r,t,o)}function H({routes:r,children:u,...i},a){const[s,c]=(0,o.splitProps)(i,["baseUrl","currentView"]),{baseUrl:l,currentView:d}=s,[f,h]=(0,e.useState)(),p=(0,e.useMemo)(()=>S(r,a(),{...i,onLoadingChange(e){h(e&&{key:(0,o.uniqId)(),status:e})}}),[r,a,l,d]);(0,e.useEffect)(()=>{(0,n.setOptions)(p,{...c,onLoadingChange(e){h(e&&{key:(0,o.uniqId)(),status:e})}})},[p,c]);const x=(0,e.useMemo)(()=>(0,t.jsx)(R,{router:p,children:u}),[p,u]);return(0,t.jsx)(y.Provider,{value:f,children:x})}function M(e){return H(e,r.createBrowserHistory)}function V(e){return H(e,r.createHashHistory)}function q({initialEntries:t,initialIndex:n,...o}){return H(o,(0,e.useMemo)(()=>()=>(0,r.createMemoryHistory)({initialEntries:t,initialIndex:n}),[t,n]))}function k(){const r=(0,e.useContext)(O);if(!r)throw new Error("useRouter() must be used within a <Router> component");return r}var E="_nativeRouterReactSSRData";function I(e,r,o){return(0,n.resolve)(e,r).then(n=>{const u=function(e){return P.get(e)}(n),i=e.history.location.state?.index||0;return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(R,{router:e,children:n}),(0,t.jsx)("script",{...o?.scriptAttributes,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`window.${o?.hydrateKey||E} = ${a={data:u,location:r,index:i},JSON.stringify(a).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")};`}})]});var a})}function A(e,t,{scriptAttributes:u,hydrateKey:i,...a}={}){const s=(0,n.create)(e,(0,r.createMemoryHistory)({initialEntries:[t]}),C,a);return I(s,(0,o.isString)(t)?(0,n.toLocation)(s,t):t,{scriptAttributes:u,hydrateKey:i})}function L(e,t){const{data:o,location:u,index:i=0}=window[t?.hydrateKey||E],a=(0,r.createBrowserHistory)();a.replace((0,r.createPath)(a.location),{index:i});const s=(0,n.create)(e,a,function(e){return(r,t)=>w(r,t,(r,t)=>e[t.index])}(o),t);return(0,n.resolve)(s,u).then(e=>({view:e,router:s}))}Object.defineProperty(exports,"HashRouter",{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,"HistoryRouter",{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,"MemoryRouter",{enumerable:!0,get:function(){return q}}),Object.defineProperty(exports,"Router",{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,"View",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"createRouter",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"hydrate",{enumerable:!0,get:function(){return L}}),Object.defineProperty(exports,"resolveServerView",{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,"resolveView",{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,"useData",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"useLoading",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"useMatched",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"useNamedData",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"useRouter",{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,"useView",{enumerable:!0,get:function(){return s}});
|
|
2
|
+
//# sourceMappingURL=ssr-C-Bgsx5W.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-C-Bgsx5W.cjs","names":[],"sources":["../src/context.tsx","../src/components/RouteErrorBoundary.tsx","../src/resolve-view.tsx","../src/components/Router.tsx","../src/ssr.tsx"],"sourcesContent":["import {createContext, ReactNode, useContext, useMemo} from 'react';\nimport type {Context, LoadStatus, Route} from './types';\n\nconst ViewContext = createContext<ReactNode>(null);\n\nexport function ViewProvider(props: {children: ReactNode; value: ReactNode}) {\n return <ViewContext.Provider {...props} />;\n}\n\n/**\n * @group Hooks\n * @see {@link View View Component}\n */\nexport function useView() {\n return useContext(ViewContext);\n}\n\n/**\n * Route-level pending skeleton(`pendingComponent` of the nearest matched\n * ancestor), set by the Router only while a navigation is pending with no\n * previous view to retain(cold start, refresh, re-navigation after an\n * error); `null` otherwise, so in-app navigation keeps the previous view.\n * @see {@link Route.pendingComponent}\n */\nexport const PendingContext = createContext<ReactNode>(null);\n\n/**\n * Used for route component to render child route component.\n * It just render the return of {@link useView}, falling back to the\n * route-level pending skeleton when the view slot is empty.\n * @group Components\n */\nexport function View() {\n const view = useView();\n const pending = useContext(PendingContext);\n return view ?? pending;\n}\n\nconst DataContext = createContext<[any, Record<string, any>]>([undefined, {}]);\n\nfunction useDataContext() {\n return useContext(DataContext);\n}\n\n/**\n * Get the named data map of the resolved route levels: an object keyed by\n * each ancestor's `name`, holding its resolved `data`. The current level\n * is included only when it declares a `name`.\n *\n * Give the generic the expected map shape to read values type-safely:\n * `useNamedData<{user: User}>()`.\n * @group Hooks\n */\nexport function useNamedData<T = Record<string, unknown>>() {\n return useDataContext()[1] as T;\n}\n\nexport function DataProvider({\n children,\n name,\n data\n}: {\n children: ReactNode;\n data: any;\n name?: string;\n}) {\n const namedData = useNamedData();\n const value = useMemo(\n () => [data, name ? {...namedData, [name]: data} : namedData] as [any, any],\n [data, name, namedData]\n );\n return <DataContext.Provider value={value}>{children}</DataContext.Provider>;\n}\n\nexport const MatchedContext = createContext<Context<Route> | undefined>(\n undefined\n);\n\n/**\n * @group Hooks\n */\nexport function useMatched() {\n return useContext(MatchedContext)!;\n}\n\n/**\n * Get the resolved `data` of the current route level, or the named data\n * of an ancestor level when `name` is given.\n *\n * Give the generic the expected data type to read it type-safely without\n * a cast: `useData<Article>()` → `Article | undefined`.\n * @group Hooks\n */\nexport function useData<T = unknown>(name?: string): T | undefined {\n const [data, namedData] = useDataContext();\n return (name ? namedData[name] : data) as T | undefined;\n}\n\nexport const LoadingContext = createContext<LoadStatus | undefined>(undefined);\n\n/**\n * @group Hooks\n */\nexport function useLoading() {\n return useContext(LoadingContext);\n}\n","import {Component, type ReactNode} from 'react';\nimport type {Context, Route} from '@@/types';\nimport type {RouterInstance} from '@native-router/core';\n\ntype Props = {\n route: Route;\n ctx: Context<Route>;\n router: RouterInstance<Route, ReactNode>;\n children: ReactNode;\n};\n\ntype State = {error?: Error};\n\n/**\n * Route-level render error boundary: catches errors thrown while the\n * level's component subtree renders and shows the route's\n * `errorComponent` with `ctx.phase === 'render'` — the render-phase\n * twin of the resolve-phase fallback in resolve-view. Just like the\n * browser renders an error page for any failed load, no rendering error\n * of a resolved view should crash past its route.\n *\n * Without a route `errorComponent` the error goes to the global\n * `errorHandler`: a returned view renders in place, while the default\n * handler(plain rejection) and async fallbacks rethrow, resurfacing the\n * error up the React tree like any unhandled error.\n */\nexport default class RouteErrorBoundary extends Component<Props, State> {\n state: State = {};\n\n static getDerivedStateFromError(error: Error): State {\n return {error};\n }\n\n render() {\n const {error} = this.state;\n if (!error) return this.props.children;\n const {route, ctx, router} = this.props;\n const ErrorComponent = route.errorComponent;\n if (ErrorComponent) {\n return <ErrorComponent error={error} ctx={{...ctx, phase: 'render'}} />;\n }\n // No route-level fallback: hand the error to the global errorHandler.\n // An async fallback cannot render synchronously; observe its rejection\n // and resurface the error itself instead.\n const fallback = router.errorHandler?.(error);\n if (fallback instanceof Promise) {\n fallback.catch(() => undefined);\n throw error;\n }\n if (fallback !== undefined) return fallback;\n throw error;\n }\n}\n","import type {ComponentType, ReactElement} from 'react';\nimport type {Context, ResolveViewContext, Route} from '@@/types';\nimport {\n mergeMatchedParams,\n parseSearch,\n parseSearchInput\n} from '@native-router/core';\nimport type {Matched} from '@native-router/core';\nimport {DataProvider, MatchedContext, View, ViewProvider} from './context';\nimport RouteErrorBoundary from './components/RouteErrorBoundary';\n\n/**\n * The default implementation of resolve view\n * @param matched the matched result\n * @param viewContext resolved view context\n * @returns the resolve view\n * @see {@link create router->create}\n */\nexport default function resolveView(\n matched: Matched<Route>[],\n ctx: ResolveViewContext<Route>\n) {\n return resolveViewBase(matched, ctx, (data, dataCtx) => data?.(dataCtx));\n}\n\nconst viewDataMap = new WeakMap<ReactElement, any[]>();\n\nexport function resolveViewServer(\n matched: Matched<Route>[],\n ctx: ResolveViewContext<Route>\n) {\n const dataResults = new Array(matched.length);\n return resolveViewBase(matched, ctx, (data, dataCtx) =>\n Promise.resolve(data?.(dataCtx)).then(\n (result) => (dataResults[dataCtx.index] = result)\n )\n ).then((view) => {\n viewDataMap.set(view, dataResults);\n return view;\n });\n}\n\nexport function createHydrateResolveView(data: any[]) {\n return (matched: Matched<Route>[], ctx: ResolveViewContext<Route>) =>\n resolveViewBase(matched, ctx, (_, dataCtx) => data[dataCtx.index]);\n}\n\nexport function getViewData(view: ReactElement) {\n return viewDataMap.get(view);\n}\n\nfunction resolveViewBase(\n matched: Matched<Route>[],\n {router, location, signal}: ResolveViewContext<Route>,\n resolveData: (\n dataFetcher: ((ctx: Context<Route>) => any) | undefined,\n ctx: Context<Route>\n ) => any\n) {\n return Promise.all(\n matched.map(({route}, index) => {\n // `search` starts as the degraded input and is upgraded to the\n // schema output before the data fetcher runs below; schema outputs\n // are user-typed(`Route<P, S>`), so the property stays `any` here.\n const ctx: Context<Route, Record<string, string>, any> = {\n matched: matched!,\n params: mergeMatchedParams(matched, index),\n index,\n router,\n location,\n search: parseSearchInput(location.search),\n // The chain's abort signal(navigation-superseded/cancelled) is\n // forwarded to every level's loader; a hand-rolled resolveView\n // context without one still yields a never-aborting signal.\n signal: signal ?? new AbortController().signal\n };\n function resolveComponent(): ComponentType | Promise<ComponentType> {\n if (!route.component) return View;\n const r = route.component(ctx);\n return Promise.resolve(r).then((m) => ('default' in m ? m.default : m));\n }\n\n // The level's search schema runs before its data fetcher: the parsed\n // output replaces the degraded input in `ctx.search`, and a rejected\n // validation fails the level exactly like a data error.\n const resolveDataWithSearch = () =>\n route.search\n ? parseSearch(route.search, location.search).then((search) => {\n ctx.search = search;\n return resolveData(route.data, ctx);\n })\n : resolveData(route.data, ctx);\n\n // A level that fails to resolve (search, data or component) is\n // replaced by its route-level errorComponent when configured;\n // otherwise the error bubbles up to the global errorHandler as before.\n return Promise.all([resolveDataWithSearch(), resolveComponent()]).then(\n ([data, C]) => (\n // The boundary is the render-phase twin of the resolve-phase\n // fallback below: a component that throws while rendering is\n // caught here and rendered through the same route errorComponent\n // (with ctx.phase === 'render'), instead of crashing past the\n // route to the React root.\n // Keyed by the level's path so React never reuses one route's\n // boundary fiber for another's at the same slot: a retained\n // error state would otherwise leak across routes when the\n // tree diff lands the same position(the class instance — and\n // its `state.error` — survives the prop change, and React\n // replays the cached error during the swap). The level index\n // only disambiguates same-path levels of one chain.\n\n <RouteErrorBoundary\n // eslint-disable-next-line @eslint-react/no-array-index-key -- not a list key: a per-level boundary identity (path + level)\n key={`${index}:${route.path ?? ''}`}\n route={route}\n ctx={ctx}\n router={router}\n >\n <DataProvider data={data} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <C />\n </MatchedContext.Provider>\n </DataProvider>\n </RouteErrorBoundary>\n ),\n (error: Error) => {\n if (!route.errorComponent) throw error;\n return (\n <DataProvider data={undefined} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <route.errorComponent error={error} ctx={ctx} />\n </MatchedContext.Provider>\n </DataProvider>\n );\n }\n );\n })\n ).then((views) =>\n views\n .reverse()\n .reduce((acc, view) => <ViewProvider value={acc}>{view}</ViewProvider>)\n );\n}\n","import {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {\n History,\n createBrowserHistory,\n createHashHistory,\n createMemoryHistory,\n MemoryHistoryOptions\n} from 'history';\nimport type {LoadStatus, Route} from '@@/types';\nimport {LoadingContext, PendingContext, ViewProvider} from '@@/context';\nimport {\n create,\n getCurrentView,\n listen,\n match,\n setOptions\n} from '@native-router/core';\nimport type {Options, ResolveView, RouterInstance} from '@native-router/core';\nimport {splitProps, uniqId} from '@native-router/core/util';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport defaultResolve from '@@/resolve-view';\n\nconst RouterContext = createContext<RouterInstance<Route, ReactNode> | null>(\n null\n);\n\ntype Props = {\n children?: ReactNode;\n routes: Route[] | Route;\n resolveView?: typeof defaultResolve;\n} & Omit<Options<ReactNode>, 'onLoadingChange'>;\n\n/**\n * Base Router Component.\n * @group Components\n */\nexport function Router({\n router,\n children\n}: {\n children?: ReactNode;\n router: RouterInstance<Route, ReactNode>;\n}) {\n const viewRef = useRef<ReactNode>(getCurrentView(router));\n const subscribe = useCallback(\n (onStoreChange: () => void) =>\n listen(router, (view) => {\n viewRef.current = view;\n onStoreChange();\n }),\n [router]\n );\n const getSnapshot = useCallback(() => viewRef.current, []);\n // `getServerSnapshot` is required by the native implementation when the\n // Router is rendered inside server-rendered content(e.g. resolveServerView).\n const getServerSnapshot = useCallback(() => getCurrentView(router), [router]);\n const view = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n // Route-level pending skeleton, only when no previous view is retained\n // (cold start, refresh, re-navigation after an error); in-app navigation\n // keeps the old view by design, the global loading signal already\n // covers that phase. Reading LoadingContext here also re-renders the\n // Router on every loading transition.\n const loading = useContext(LoadingContext);\n const pending =\n view == null && loading?.status === 'pending'\n ? resolvePendingView(router, loading.key)\n : null;\n\n return (\n <RouterContext.Provider value={router}>\n {children === undefined ? (\n (view ?? pending)\n ) : (\n <ViewProvider value={view}>\n <PendingContext.Provider value={pending}>\n {children}\n </PendingContext.Provider>\n </ViewProvider>\n )}\n </RouterContext.Provider>\n );\n}\n\n/**\n * Render the `pendingComponent` of the nearest matched ancestor of the\n * resolving location, walked deepest first(the resolving route's own\n * included). Keyed by the loading episode so a new pending phase remounts\n * the skeleton(stateful shimmer animations restart). Guards may still\n * redirect the resolution away; until then the initially matched chain\n * is the best — and only — answer available.\n */\nfunction resolvePendingView(\n router: RouterInstance<Route, ReactNode>,\n key: number\n) {\n const {resolving} = router;\n const matched = resolving ? match(router, resolving.pathname) : undefined;\n if (!matched) return null;\n for (let i = matched.length - 1; i >= 0; i--) {\n const Pending = matched[i].route.pendingComponent;\n if (Pending) return <Pending key={key} />;\n }\n return null;\n}\n\nexport function createRouter(\n routes: Route | Route[],\n history: History,\n {\n resolveView = defaultResolve,\n ...options\n }: Options<ReactNode> & {resolveView?: ResolveView<Route, ReactNode>} = {}\n): RouterInstance<Route, ReactNode> {\n return create(routes, history, resolveView, options);\n}\n\nfunction useNewRouter(\n {routes, children, ...options}: Props,\n createHistory: () => History\n) {\n const [tracked, rest] = splitProps(options, ['baseUrl', 'currentView']);\n const {baseUrl, currentView} = tracked;\n const [loading, setLoading] = useState<LoadStatus>();\n // Initial options are baked in at creation: the cold-start resolve fires\n // from the subscribe effect(children effects run first) before the\n // setOptions effect below runs, and the default errorHandler would let\n // listen's refresh().catch(noop) swallow a first failure, leaving the\n // view blank forever.\n const router = useMemo(\n () =>\n createRouter(routes, createHistory(), {\n ...options,\n onLoadingChange(status) {\n setLoading(status && {key: uniqId(), status});\n }\n }),\n // Only the tracked options belong to the deps: option updates flow\n // through the setOptions effect below instead of recreating the router.\n [routes, createHistory, baseUrl, currentView]\n );\n\n // Options are refreshed on every commit, so `onLoadingChange` and the\n // callback options always see the latest closure.\n useEffect(() => {\n setOptions(router, {\n ...rest,\n onLoadingChange(status) {\n setLoading(status && {key: uniqId(), status});\n }\n });\n }, [router, rest]);\n\n const r = useMemo(\n () => <Router router={router}>{children}</Router>,\n [router, children]\n );\n\n return <LoadingContext.Provider value={loading}>{r}</LoadingContext.Provider>;\n}\n\n/**\n * History mode Router Component.\n * @group Components\n */\nexport function HistoryRouter(props: Props) {\n return useNewRouter(props, createBrowserHistory);\n}\n\n/**\n * Hash mode Router Component.\n * @group Components\n */\nexport function HashRouter(props: Props) {\n return useNewRouter(props, createHashHistory);\n}\n\n/**\n * Memory mode Router Component.\n * @group Components\n */\nexport function MemoryRouter({\n initialEntries,\n initialIndex,\n ...props\n}: Props & MemoryHistoryOptions) {\n const createHistory = useMemo(\n () => () => createMemoryHistory({initialEntries, initialIndex}),\n [initialEntries, initialIndex]\n );\n return useNewRouter(props, createHistory);\n}\n\n/**\n * Get Router instance.\n * @group Hooks\n * @returns Router Instance\n */\nexport function useRouter() {\n const router = useContext(RouterContext);\n if (!router) {\n throw new Error('useRouter() must be used within a <Router> component');\n }\n return router;\n}\n","import {createBrowserHistory, createMemoryHistory, createPath} from 'history';\nimport {ReactElement, ReactNode} from 'react';\nimport {create, resolve, toLocation} from '@native-router/core';\nimport type {\n HistoryState,\n Location,\n Options,\n RouterInstance\n} from '@native-router/core';\nimport {isString} from '@native-router/core/util';\nimport {Router} from './components/Router';\nimport {\n createHydrateResolveView,\n getViewData,\n resolveViewServer\n} from './resolve-view';\nimport type {Route} from './types';\n\nconst defaultHydrateKey = '_nativeRouterReactSSRData';\n\n/**\n * Serialize the SSR payload for embedding in a script element.\n * `JSON.stringify` does not escape `<`, U+2028 and U+2029,\n * so route data like `</script>` would break out of the script element.\n * @param payload the payload to serialize\n * @returns the escaped JSON string\n */\nfunction serializePayload(payload: {\n data?: any[];\n location: Location;\n index: number;\n}) {\n return JSON.stringify(payload)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nexport function resolveServerViewBase(\n router: RouterInstance<Route, ReactNode>,\n location: Location,\n options?: {\n scriptAttributes?: Record<string, string>;\n hydrateKey?: string;\n }\n) {\n return resolve<Route, ReactNode>(router, location).then((view) => {\n const data = getViewData(view as ReactElement);\n const index =\n (router.history.location.state as HistoryState | undefined)?.index || 0;\n return (\n <>\n <Router router={router}>{view}</Router>\n <script\n {...options?.scriptAttributes}\n suppressHydrationWarning\n // eslint-disable-next-line @eslint-react/dom-no-dangerously-set-innerhtml -- serialized state for hydration\n dangerouslySetInnerHTML={{\n __html: `window.${\n options?.hydrateKey || defaultHydrateKey\n } = ${serializePayload({data, location, index})};`\n }}\n />\n </>\n );\n });\n}\n\nexport function resolveServerView(\n routes: Route | Route[],\n location: Location | string,\n {\n scriptAttributes,\n hydrateKey,\n ...options\n }: Options<ReactElement> & {\n scriptAttributes?: Record<string, string>;\n hydrateKey?: string;\n } = {}\n) {\n const router = create(\n routes,\n createMemoryHistory({initialEntries: [location]}),\n resolveViewServer,\n options\n );\n\n return resolveServerViewBase(\n router,\n isString(location) ? toLocation(router, location) : location,\n {\n scriptAttributes,\n hydrateKey\n }\n );\n}\n\n/**\n * Hydrate the SSR result of {@link resolveServerView} on the client.\n * The router is bound to the browser history(aligned with the index\n * in the SSR payload), so navigation after hydration updates the address bar.\n * @param routes routes config, must match the server side\n * @param options options, `hydrateKey` must match the server side\n * @returns the resolved view and the router instance, for example:\n * `const {view, router} = await hydrate(routes);`\n * `hydrateRoot(root, <Router router={router}>{view}</Router>)`\n * @group Methods\n */\nexport function hydrate(\n routes: Route | Route[],\n options?: Options<ReactElement> & {\n hydrateKey?: string;\n }\n): Promise<{view: ReactNode; router: RouterInstance<Route, ReactNode>}> {\n const {\n data,\n location,\n index = 0\n } = (window as any)[options?.hydrateKey || defaultHydrateKey] as {\n data: any[];\n location: Location;\n index?: number;\n };\n const history = createBrowserHistory();\n history.replace(createPath(history.location), {index});\n const router = create(\n routes,\n history,\n createHydrateResolveView(data),\n options\n );\n return resolve<Route, ReactNode>(router, location).then((view) => ({\n view,\n router\n }));\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { LinkProps } from '../types';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { AsLinkProps, LinkProps } from '../types';
|
|
2
|
+
import { type ElementType, type ReactElement } from 'react';
|
|
3
|
+
declare const Link: {
|
|
4
|
+
<A extends ElementType = "a">(props: AsLinkProps<LinkProps, A>): ReactElement | null;
|
|
5
|
+
(props: LinkProps): ReactElement | null;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
};
|
|
8
|
+
export default Link;
|
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
import type { NavLinkProps } from '../types';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* unless `caseSensitive` is set.
|
|
10
|
-
* - `isExactActive`: `current === target`.
|
|
11
|
-
* - `isActive`: `isExactActive` when `end` is set, otherwise `current` equals
|
|
12
|
-
* `target` or starts with `target` plus a trailing `/`(so `to="/"` is active
|
|
13
|
-
* for every path).
|
|
14
|
-
*
|
|
15
|
-
* While active the anchor renders `aria-current={ariaCurrent ?? 'page'}` and
|
|
16
|
-
* `className`/`style`/`children` receive the active state when given as
|
|
17
|
-
* functions. Click behavior is delegated to {@link Link}, inheriting the
|
|
18
|
-
* modified-click guard and the double-click lock.
|
|
19
|
-
* @param props
|
|
20
|
-
* @group Components
|
|
21
|
-
*/
|
|
22
|
-
export default function NavLink({ to, end, caseSensitive, className, style, ariaCurrent, children, ...rest }: NavLinkProps): import("react").JSX.Element;
|
|
1
|
+
import type { AsLinkProps, NavLinkProps } from '../types';
|
|
2
|
+
import { type ElementType, type ReactElement } from 'react';
|
|
3
|
+
declare const NavLink: {
|
|
4
|
+
<A extends ElementType = "a">(props: AsLinkProps<NavLinkProps, A>): ReactElement | null;
|
|
5
|
+
(props: NavLinkProps): ReactElement | null;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
};
|
|
8
|
+
export default NavLink;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { LinkProps } from '../types';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
1
|
+
import type { AsLinkProps, LinkProps } from '../types';
|
|
2
|
+
import { ReactNode, type ElementType, type ReactElement } from 'react';
|
|
3
3
|
type PrefetchLinkContext = {
|
|
4
4
|
loading: boolean;
|
|
5
5
|
error?: Error;
|
|
@@ -10,17 +10,9 @@ type PrefetchLinkContext = {
|
|
|
10
10
|
* @group Hooks
|
|
11
11
|
*/
|
|
12
12
|
export declare function usePrefetch(): PrefetchLinkContext;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* - `'viewport'`: prefetch when the link scrolls into the viewport.
|
|
20
|
-
* - `'none'`: never prefetch; the target is resolved on click.
|
|
21
|
-
*
|
|
22
|
-
* @param props
|
|
23
|
-
* @group Components
|
|
24
|
-
*/
|
|
25
|
-
export default function PrefetchLink({ to, prefetch, children, ...rest }: LinkProps): import("react").JSX.Element;
|
|
26
|
-
export {};
|
|
13
|
+
declare const PrefetchLink: {
|
|
14
|
+
<A extends ElementType = "a">(props: AsLinkProps<LinkProps, A>): ReactElement | null;
|
|
15
|
+
(props: LinkProps): ReactElement | null;
|
|
16
|
+
displayName?: string;
|
|
17
|
+
};
|
|
18
|
+
export default PrefetchLink;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Component, type ReactNode } from 'react';
|
|
2
|
+
import type { Context, Route } from '../types';
|
|
3
|
+
import type { RouterInstance } from '@native-router/core';
|
|
4
|
+
type Props = {
|
|
5
|
+
route: Route;
|
|
6
|
+
ctx: Context<Route>;
|
|
7
|
+
router: RouterInstance<Route, ReactNode>;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
type State = {
|
|
11
|
+
error?: Error;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Route-level render error boundary: catches errors thrown while the
|
|
15
|
+
* level's component subtree renders and shows the route's
|
|
16
|
+
* `errorComponent` with `ctx.phase === 'render'` — the render-phase
|
|
17
|
+
* twin of the resolve-phase fallback in resolve-view. Just like the
|
|
18
|
+
* browser renders an error page for any failed load, no rendering error
|
|
19
|
+
* of a resolved view should crash past its route.
|
|
20
|
+
*
|
|
21
|
+
* Without a route `errorComponent` the error goes to the global
|
|
22
|
+
* `errorHandler`: a returned view renders in place, while the default
|
|
23
|
+
* handler(plain rejection) and async fallbacks rethrow, resurfacing the
|
|
24
|
+
* error up the React tree like any unhandled error.
|
|
25
|
+
*/
|
|
26
|
+
export default class RouteErrorBoundary extends Component<Props, State> {
|
|
27
|
+
state: State;
|
|
28
|
+
static getDerivedStateFromError(error: Error): State;
|
|
29
|
+
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ElementType, type ReactElement } from 'react';
|
|
2
|
+
import type { AsLinkProps, TypedLinkProps } from '../types';
|
|
3
|
+
declare const TypedLink: {
|
|
4
|
+
<Paths extends string = string, A extends ElementType = "a">(props: AsLinkProps<TypedLinkProps<Paths>, A>): ReactElement | null;
|
|
5
|
+
<Paths extends string = string>(props: TypedLinkProps<Paths>): ReactElement | null;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
};
|
|
8
|
+
export default TypedLink;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Route } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Identity function with `satisfies` semantics: the table is checked
|
|
4
|
+
* against `Route` while every `path` keeps its string-literal type, so
|
|
5
|
+
* `RoutePaths<typeof routes>` can extract the full pattern union for
|
|
6
|
+
* `TypedLink`. An `as Route` assertion does the opposite — it widens
|
|
7
|
+
* every `path` to `string` and gives up the literals.
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const routes = createRoutes({
|
|
11
|
+
* children: [
|
|
12
|
+
* {path: '/', component: () => import('./Home')},
|
|
13
|
+
* {path: '/users/:id', component: () => import('./UserProfile')}
|
|
14
|
+
* ]
|
|
15
|
+
* });
|
|
16
|
+
* // type AppPaths = '/' | '/users/:id'
|
|
17
|
+
* type AppPaths = RoutePaths<typeof routes>;
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Zero runtime cost: the function returns its argument unchanged and
|
|
21
|
+
* tree-shakes away.
|
|
22
|
+
* @group Methods
|
|
23
|
+
* @category Route
|
|
24
|
+
* @param routes the route table, a route object or an array of them
|
|
25
|
+
* @returns the very same route table, literal types preserved
|
|
26
|
+
*/
|
|
27
|
+
export declare function createRoutes<const T>(routes: T & (Route | Route[])): T;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,8 +3,11 @@ export { default as Link } from './components/Link';
|
|
|
3
3
|
export { default as NavLink } from './components/NavLink';
|
|
4
4
|
export { default as PrefetchLink, usePrefetch } from './components/PrefetchLink';
|
|
5
5
|
export { default as ScrollRestoration } from './components/ScrollRestoration';
|
|
6
|
+
export { default as TypedLink } from './components/TypedLink';
|
|
7
|
+
export { createRoutes } from './create-routes';
|
|
6
8
|
export { useView, View, useData, useNamedData, useLoading, useMatched } from './context';
|
|
7
|
-
export { useSearchParams, useSearch } from './use-search-params';
|
|
9
|
+
export { useSearchParams, useSearch, useSetSearch } from './use-search-params';
|
|
10
|
+
export { useBlocker } from './use-blocker';
|
|
8
11
|
export { default as defaultResolveView } from './resolve-view';
|
|
9
12
|
export * from './types';
|
|
10
13
|
export { hydrate } from './ssr';
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnchorHTMLAttributes, ComponentType, CSSProperties, DetailedHTMLProps, ReactNode } from 'react';
|
|
1
|
+
import type { AnchorHTMLAttributes, ComponentPropsWithRef, ComponentPropsWithoutRef, ComponentType, CSSProperties, DetailedHTMLProps, ElementType, ReactNode } from 'react';
|
|
2
2
|
import type { BaseRoute, ExtractPathParams, Matched, Location, RouterInstance, SearchInput } from '@native-router/core';
|
|
3
3
|
export type ResolveViewContext<R extends BaseRoute> = {
|
|
4
4
|
router: RouterInstance<R>;
|
|
@@ -32,6 +32,13 @@ export type Context<T extends BaseRoute, P = Record<string, string>, S = SearchI
|
|
|
32
32
|
* consuming the network instead of only having its result dropped.
|
|
33
33
|
*/
|
|
34
34
|
signal: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* Which error phase an `errorComponent` is being rendered for. Absent
|
|
37
|
+
* during the resolve phase(loader/guard/search failures — the fallback
|
|
38
|
+
* of `resolve-view`); `'render'` when the component subtree threw while
|
|
39
|
+
* rendering and the route-level error boundary caught it.
|
|
40
|
+
*/
|
|
41
|
+
phase?: 'render';
|
|
35
42
|
};
|
|
36
43
|
/**
|
|
37
44
|
* Params shape of a route path. Literal patterns get a precise shape via
|
|
@@ -70,7 +77,11 @@ export type Route<P extends string = string, S = any> = Omit<BaseRoute<{
|
|
|
70
77
|
/**
|
|
71
78
|
* Not parametrized by `P`: props are strictly contravariant, so a
|
|
72
79
|
* precise params type here would break assignability between
|
|
73
|
-
* `Route<'/a/:id'>` and plain `Route`.
|
|
80
|
+
* `Route<'/a/:id'>` and plain `Route`. Rendered for both error
|
|
81
|
+
* phases: resolve failures(search/data/component load, no
|
|
82
|
+
* `ctx.phase`) and render errors thrown by the level's component
|
|
83
|
+
* subtree(`ctx.phase === 'render'`, caught by the route-level
|
|
84
|
+
* render error boundary).
|
|
74
85
|
*/
|
|
75
86
|
errorComponent?: ComponentType<{
|
|
76
87
|
error: Error;
|
|
@@ -103,6 +114,76 @@ export type LoadStatus = {
|
|
|
103
114
|
key: number;
|
|
104
115
|
status: 'pending' | 'resolved' | 'rejected';
|
|
105
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Union of every navigable path pattern of a route table, computed from
|
|
119
|
+
* the table's type. Each level's `path` literal concatenates with its
|
|
120
|
+
* children's patterns the way the runtime matcher consumes them
|
|
121
|
+
* (`{path: '/users', children: [{path: '/:id'}]}` → `'/users/:id'`),
|
|
122
|
+
* layout levels without `path` pass their children's patterns through,
|
|
123
|
+
* and param segments stay in the union, e.g. `'/article/:title'`.
|
|
124
|
+
*
|
|
125
|
+
* The table must keep its `path` literal types: build it with
|
|
126
|
+
* {@link createRoutes}(satisfies semantics) or annotate levels with
|
|
127
|
+
* `Route<'/literal'>`. An `as Route` assertion widens every `path` to
|
|
128
|
+
* `string`, the union degrades to `string`, and {@link TypedLink}
|
|
129
|
+
* accepts any path — exactly like a plain `Link`.
|
|
130
|
+
* @group Types
|
|
131
|
+
* @category Route
|
|
132
|
+
*/
|
|
133
|
+
export type RoutePaths<Routes> = Routes extends readonly (infer R)[] ? RoutePathsOf<R> : RoutePathsOf<Routes>;
|
|
134
|
+
/**
|
|
135
|
+
* One level's contribution: with `children`, only the concatenated
|
|
136
|
+
* parent+child patterns are navigable(the runtime matcher requires a
|
|
137
|
+
* child — or a `path: ''` leaf — to consume the remainder); without
|
|
138
|
+
* children, the level's own pattern. A widened `path`(`string`, e.g.
|
|
139
|
+
* an `as Route` assertion) short-circuits to `string` — which also
|
|
140
|
+
* stops the recursion over the self-referential `Route` type. A
|
|
141
|
+
* `path`-less layout level(P `undefined`) recurses into its children.
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* One level's contribution: with `children`, only the concatenated
|
|
145
|
+
* parent+child patterns are navigable(the runtime matcher requires a
|
|
146
|
+
* child — or a `path: ''` leaf — to consume the remainder); without
|
|
147
|
+
* children, the level's own pattern.
|
|
148
|
+
*
|
|
149
|
+
* The widened short-circuits matter: a `path`-less layout level infers
|
|
150
|
+
* `P = unknown`(optional-property inference) and recurses into its
|
|
151
|
+
* children; a widened `path: string`(an `as Route` assertion) yields
|
|
152
|
+
* `string` — which also stops the recursion over the self-referential
|
|
153
|
+
* `Route` type, where the non-literal child paths degrade to `string`.
|
|
154
|
+
*/
|
|
155
|
+
type RoutePathsOf<R> = R extends {
|
|
156
|
+
path?: infer P;
|
|
157
|
+
children?: infer C;
|
|
158
|
+
} ? [P] extends [never] ? never : unknown extends P ? C extends readonly unknown[] ? RoutePaths<C> : never : string extends P ? string : C extends readonly unknown[] ? ParentPaths<P, RoutePaths<C>> : OwnPath<P> : never;
|
|
159
|
+
type OwnPath<P> = P extends string ? P : never;
|
|
160
|
+
/**
|
|
161
|
+
* Prefix every child pattern with the parent's pattern
|
|
162
|
+
* (`'/users'` + `'/:id'` → `'/users/:id'`); a layout parent without
|
|
163
|
+
* `path` contributes nothing. A non-literal(widened) side degrades the
|
|
164
|
+
* whole union to `string`.
|
|
165
|
+
*/
|
|
166
|
+
type ParentPaths<P, ChildPaths> = string extends ChildPaths ? string : ChildPaths extends string ? P extends string ? string extends P ? string : `${P}${ChildPaths}` : ChildPaths : never;
|
|
167
|
+
/**
|
|
168
|
+
* Props of {@link TypedLink}: a discriminated union over the table's
|
|
169
|
+
* path patterns, so `params` is checked against the param segments of
|
|
170
|
+
* the exact `to` pattern — omitted for static patterns, required with
|
|
171
|
+
* `{name: string}` for `:name` segments and `{name: string[]}` for
|
|
172
|
+
* `*name` wildcards(see {@link RouteParams}).
|
|
173
|
+
*
|
|
174
|
+
* Give the component the table's pattern union as its type argument:
|
|
175
|
+
* `TypedLink<RoutePaths<typeof routes>>`.
|
|
176
|
+
* @group Types
|
|
177
|
+
* @category Route
|
|
178
|
+
*/
|
|
179
|
+
export type TypedLinkProps<Paths extends string = string> = {
|
|
180
|
+
[P in Paths]: Record<never, never> extends RouteParams<P> ? {
|
|
181
|
+
to: P;
|
|
182
|
+
} : {
|
|
183
|
+
to: P;
|
|
184
|
+
params: RouteParams<P>;
|
|
185
|
+
};
|
|
186
|
+
}[Paths] & Omit<LinkProps, 'to' | 'prefetch' | 'href'>;
|
|
106
187
|
export type LinkProps = {
|
|
107
188
|
to: string;
|
|
108
189
|
/**
|
|
@@ -112,6 +193,68 @@ export type LinkProps = {
|
|
|
112
193
|
prefetch?: 'intent' | 'render' | 'viewport' | 'none';
|
|
113
194
|
children?: ReactNode;
|
|
114
195
|
} & DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
196
|
+
/**
|
|
197
|
+
* Keys owned by the link components themselves when rendering through an
|
|
198
|
+
* `as` component: the injection surface(`href`, the composed `onClick`,
|
|
199
|
+
* NavLink's `aria-current`) plus React's reserved keys and the polymorphism
|
|
200
|
+
* props' own names. They are stripped from the flattened `as`-props region
|
|
201
|
+
* and from `asProps`, so a same-named prop of the `as` component can never
|
|
202
|
+
* interfere with the navigation semantics.
|
|
203
|
+
*/
|
|
204
|
+
type AsManagedKeys = 'as' | 'asProps' | 'ref' | 'key' | 'href' | 'onClick' | 'aria-current';
|
|
205
|
+
/**
|
|
206
|
+
* Union-preserving `Omit`: the built-in collapses unions(`Omit<A | B, K>`
|
|
207
|
+
* picks across the members), which would flatten the discriminated union
|
|
208
|
+
* of {@link TypedLinkProps}.
|
|
209
|
+
*/
|
|
210
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
211
|
+
/**
|
|
212
|
+
* Union of every key appearing on any member: `keyof (A | B)` intersects
|
|
213
|
+
* the members' keys, so a key living on only part of a union(`params` on
|
|
214
|
+
* {@link TypedLinkProps}) would escape an `Omit` keyed on plain `keyof`.
|
|
215
|
+
*/
|
|
216
|
+
type KeysOfUnion<T> = T extends unknown ? keyof T : never;
|
|
217
|
+
/**
|
|
218
|
+
* The `ref` an `as` component accepts — `{ref?: never}` when it does not
|
|
219
|
+
* take one(i.e. is not wrapped in `forwardRef`), so passing a ref to such
|
|
220
|
+
* a component is a compile error. The detection relies on how
|
|
221
|
+
* `ComponentPropsWithRef` is typed: guaranteed under `@types/react` ≥ 19;
|
|
222
|
+
* under `@types/react` 18 it always includes `ref`, so the guard degrades
|
|
223
|
+
* to accepting the ref(and React warns at runtime that it is dropped).
|
|
224
|
+
*/
|
|
225
|
+
type AsRefProps<A extends ElementType> = 'ref' extends keyof ComponentPropsWithRef<A> ? Pick<ComponentPropsWithRef<A>, 'ref'> : {
|
|
226
|
+
ref?: never;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Props of the link family({@link Link}, {@link NavLink},
|
|
230
|
+
* {@link PrefetchLink}, {@link TypedLink}) when rendering through a custom
|
|
231
|
+
* `as` component. Three regions, no ambiguity:
|
|
232
|
+
*
|
|
233
|
+
* 1. The component's own props(`Base`: `to`, anchor attributes, NavLink's
|
|
234
|
+
* active-state callbacks, ...) minus its anchor `ref`.
|
|
235
|
+
* 2. The flattened `as`-props region: every prop of the `as` component
|
|
236
|
+
* that does not collide with `Base`(`variant`, `tone`, ...) is accepted
|
|
237
|
+
* directly on the link. A key appearing on any member of a union
|
|
238
|
+
* `Base` counts as colliding(`params` on {@link TypedLinkProps} is
|
|
239
|
+
* owned by the link, never by the `as` component).
|
|
240
|
+
* 3. The `asProps` escape hatch for the colliding keys: only the props the
|
|
241
|
+
* `as` component shares with `Base`(`title`, `target`, ...) may be set
|
|
242
|
+
* there — `Pick` degrades to `{}` when there is no overlap — minus the
|
|
243
|
+
* managed keys(see {@link AsManagedKeys}), and it is spread last at
|
|
244
|
+
* runtime, explicitly overriding the base value.
|
|
245
|
+
*
|
|
246
|
+
* `href`, the composed `onClick` and NavLink's `aria-current` are always
|
|
247
|
+
* injected by the link itself(see {@link AsManagedKeys}) and win over
|
|
248
|
+
* everything else — neither the flattened region nor `asProps` can set
|
|
249
|
+
* them; the `ref` is the `as` component's own, so it must be
|
|
250
|
+
* ref-forwarding for `ref` to type-check.
|
|
251
|
+
* @group Types
|
|
252
|
+
* @category Link
|
|
253
|
+
*/
|
|
254
|
+
export type AsLinkProps<Base, A extends ElementType> = {
|
|
255
|
+
as?: A;
|
|
256
|
+
asProps?: Omit<Pick<ComponentPropsWithoutRef<A>, keyof Base & keyof ComponentPropsWithoutRef<A>>, AsManagedKeys>;
|
|
257
|
+
} & DistributiveOmit<Base, 'ref'> & Omit<ComponentPropsWithoutRef<A>, KeysOfUnion<Base> | AsManagedKeys> & AsRefProps<A>;
|
|
115
258
|
export type { SearchInput, SearchOutputOf, StandardSchemaV1 } from '@native-router/core';
|
|
116
259
|
/**
|
|
117
260
|
* Active state passed to the render-prop / callback flavors of
|
|
@@ -136,4 +279,4 @@ export type NavLinkProps = {
|
|
|
136
279
|
/** `aria-current` value rendered while active. @default 'page' */
|
|
137
280
|
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time';
|
|
138
281
|
children?: ReactNode | ((state: NavLinkState) => ReactNode);
|
|
139
|
-
} & Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, 'className' | 'style' | 'children'
|
|
282
|
+
} & Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, 'className' | 'style' | 'children'>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BlockerFn } from '@native-router/core';
|
|
2
|
+
/**
|
|
3
|
+
* Block navigations away from the current page while the component is
|
|
4
|
+
* mounted — the unsaved-changes guard.
|
|
5
|
+
*
|
|
6
|
+
* The predicate is the core `setBlocker` veto: `(to, from) => boolean`
|
|
7
|
+
* over path strings(including search and hash), asked synchronously at
|
|
8
|
+
* the head of every navigation and before a history POP lands. Return
|
|
9
|
+
* `false` to veto: a vetoed navigation never starts and a vetoed POP is
|
|
10
|
+
* rewound. `refresh` and guard redirects are never blocked; the effect
|
|
11
|
+
* releases the blocker on unmount, so the guard lives exactly as long
|
|
12
|
+
* as the guarding component.
|
|
13
|
+
*
|
|
14
|
+
* The predicate is stored in a ref and re-synced on every render, so a
|
|
15
|
+
* navigation is always asked the latest closure — a `confirmed` flag it
|
|
16
|
+
* captured works without re-registering anything. SSR-safe: nothing
|
|
17
|
+
* here touches `window`, and the registration itself is an effect that
|
|
18
|
+
* never runs on the server.
|
|
19
|
+
*
|
|
20
|
+
* @group Hooks
|
|
21
|
+
* @param fn blocker predicate; `to` is the target path, `from` the
|
|
22
|
+
* current path
|
|
23
|
+
* @see {@link setBlocker}
|
|
24
|
+
*/
|
|
25
|
+
export declare function useBlocker(fn: BlockerFn): void;
|
|
@@ -25,6 +25,33 @@ type SetSearchParams = (next: URLSearchParams | ((prev: URLSearchParams) => URLS
|
|
|
25
25
|
* commits, so callers may optionally `await` it
|
|
26
26
|
*/
|
|
27
27
|
export declare function useSearchParams(): [URLSearchParams, SetSearchParams];
|
|
28
|
+
/**
|
|
29
|
+
* Write the search params of the current location through a schema, the
|
|
30
|
+
* setter-side twin of `useSearch(schema)`: the next value is serialized
|
|
31
|
+
* to a query string, degraded with `parseSearchInput` and validated by
|
|
32
|
+
* the SAME schema before any navigation happens. A schema that rejects
|
|
33
|
+
* the value throws its issues(`SearchError`) without touching the
|
|
34
|
+
* location; the value the schema would default or coerce on the read
|
|
35
|
+
* side never gets silently written.
|
|
36
|
+
*
|
|
37
|
+
* Synchronous schemas only(the `useSearch` flavor); an async `validate`
|
|
38
|
+
* rejects without navigating.
|
|
39
|
+
*
|
|
40
|
+
* The navigation semantics follow `useSearchParams`' setter: push by
|
|
41
|
+
* default, `{replace: true}` rewrites the current entry, guards run,
|
|
42
|
+
* and the returned `Promise<void>` resolves once the navigation commits.
|
|
43
|
+
*
|
|
44
|
+
* @group Hooks
|
|
45
|
+
* @param schema a Standard Schema validator of the search — must
|
|
46
|
+
* validate synchronously
|
|
47
|
+
* @returns the schema-aware setter; functional updates receive the live
|
|
48
|
+
* previous params
|
|
49
|
+
* @throws {SearchError} when `schema` rejects the next value, before
|
|
50
|
+
* any navigation
|
|
51
|
+
*/
|
|
52
|
+
export declare function useSetSearch<S extends StandardSchemaV1>(schema: S): (next: SearchInput | ((prev: SearchInput) => SearchInput), opts?: {
|
|
53
|
+
replace?: boolean;
|
|
54
|
+
}) => Promise<void> | void;
|
|
28
55
|
/**
|
|
29
56
|
* Read the parsed search params of the current location.
|
|
30
57
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-router/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@native-router/core": "^1.
|
|
65
|
+
"@native-router/core": "^1.7.0",
|
|
66
66
|
"history": "^5.3.0",
|
|
67
67
|
"use-sync-external-store": "^1.6.0"
|
|
68
68
|
},
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
"@linaria/vite": "^5.0.4",
|
|
79
79
|
"@testing-library/dom": "^10.4.1",
|
|
80
80
|
"@testing-library/react": "^16.3.2",
|
|
81
|
-
"@types/node": "^26.
|
|
81
|
+
"@types/node": "^26.3.0",
|
|
82
82
|
"@types/react": "^19.2.18",
|
|
83
|
-
"@types/react-dom": "^19.2.
|
|
83
|
+
"@types/react-dom": "^19.2.5",
|
|
84
84
|
"@vitejs/plugin-react": "^6.1.0",
|
|
85
85
|
"@vitest/coverage-v8": "^4.1.11",
|
|
86
86
|
"@vitest/ui": "^4.1.11",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"commitizen": "^4.3.2",
|
|
91
91
|
"core-js": "^3.50.0",
|
|
92
92
|
"cross-env": "^10.1.0",
|
|
93
|
-
"eslint": "^10.9.
|
|
93
|
+
"eslint": "^10.9.1",
|
|
94
94
|
"eslint-config-prettier": "^10.1.8",
|
|
95
95
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
96
96
|
"eslint-plugin-compat": "^7.0.2",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"typedoc-plugin-mark-react-functional-components": "^0.2.2",
|
|
116
116
|
"typedoc-plugin-missing-exports": "^4.1.4",
|
|
117
117
|
"typescript": "^6.0.3",
|
|
118
|
-
"typescript-eslint": "^8.
|
|
118
|
+
"typescript-eslint": "^8.68.0",
|
|
119
119
|
"vite": "^8.2.2",
|
|
120
120
|
"vitest": "^4.1.11"
|
|
121
121
|
}
|