@native-router/react 1.3.1 → 1.4.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 +49 -3
- 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/RouteErrorBoundary.d.ts +31 -0
- package/dist/types/components/TypedLink.d.ts +4 -0
- package/dist/types/create-routes.d.ts +27 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/types.d.ts +82 -1
- package/dist/types/use-search-params.d.ts +27 -0
- package/package.json +1 -1
- 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
package/README.md
CHANGED
|
@@ -72,13 +72,15 @@ function Preview({visible}: {visible: boolean}) {
|
|
|
72
72
|
- Route guards: static `redirect` and async `beforeLoad` on every route level, run shallow → deep; more than 10 chained redirects reject with `RedirectLoopError`
|
|
73
73
|
- Cancelable async navigation: starting a new navigation supersedes the in-flight one; `cancel(router)` aborts it; a history POP cancels it too — and the chain's `AbortSignal` reaches every `data` loader as `ctx.signal` (`fetch(url, {signal: ctx.signal})`), so superseded navigations stop their requests instead of only having results dropped
|
|
74
74
|
- `NavLink` with `isActive`/`isExactActive`, `end`, `caseSensitive` and `aria-current` (defaults to `"page"`); `className`/`style`/`children` accept `({isActive, isExactActive})` callbacks; `to="/"` is active for every path
|
|
75
|
-
- `useSearchParams` reads and writes the query string; writes push by default or replace with `{replace: true}`
|
|
75
|
+
- `useSearchParams` reads and writes the query string; writes push by default or replace with `{replace: true}`; `useSetSearch(schema)` is the schema-aware setter twin of `useSearch(schema)` — the next value is validated by the same schema before any navigation, a rejection throws `SearchError` without touching the location, and the written query is the schema's own output(defaults applied)
|
|
76
76
|
- Typed search: an optional Standard Schema validator (zod/valibot/arktype, no hard dependency) on any route `search` field, parsed at resolve time — loaders receive a typed `ctx.search` and an invalid search fails the level through the existing error layers; `useSearch(schema?)` reads it in components, degrading to the raw object without a schema
|
|
77
|
+
- Type-safe links: `createRoutes(routes)` checks the table while keeping every `path` literal, `RoutePaths<typeof routes>` extracts the pattern union(through nesting and param segments), and `<TypedLink<RoutePaths<...>> to params>` narrows `to` to the table and checks `params` against the exact pattern's segments — compile errors for unknown paths and missing/wrong params, click-time interpolation with encoding as the runtime backstop
|
|
77
78
|
- `ScrollRestoration` restores the scroll offset per history entry on back/forward and resets it on push (`resetOnPush` to opt out)
|
|
78
79
|
- Router-level `preload(router, to)` shares resolved views across links with in-flight dedup and a 30s TTL; `PrefetchLink` prefetch through it
|
|
79
|
-
- Hooks: `useRouter`, `useView`, `useData<T>(name?)` (typed data of the current level, or named data of ancestor routes), `useMatched` (matched levels, params, location), `useLoading`, `usePrefetch`, `useSearch(schema?)`
|
|
80
|
-
- Two error layers: global `errorHandler` prop on the Router, per-route `errorComponent` receiving `{error, ctx}`
|
|
80
|
+
- Hooks: `useRouter`, `useView`, `useData<T>(name?)` (typed data of the current level, or named data of ancestor routes), `useMatched` (matched levels, params, location), `useLoading`, `usePrefetch`, `useSearch(schema?)`, `useSetSearch(schema)`
|
|
81
|
+
- Two error layers, both phases: global `errorHandler` prop on the Router, per-route `errorComponent` receiving `{error, ctx}` — `errorComponent` renders for resolve failures(loader/guard/search, no `ctx.phase`) AND for render errors thrown by the component subtree(`ctx.phase === 'render'`, caught by a route-level error boundary so a rendering crash never escapes past its route, like the browser's error page for any failed load)
|
|
81
82
|
- Route-level `pendingComponent` skeleton, shown only when no previous view can be retained (cold start, refresh, re-navigation after an error); the nearest matched ancestor's wins, and in-app navigation keeps the previous view instead
|
|
83
|
+
- Keeping the previous view during in-app navigation is an intentional design following browser-native semantics — see the Design Principles section of the core repository's README
|
|
82
84
|
- SSR: `resolveServerView` (from `@native-router/react/server`) renders the view plus an inline data payload; `hydrate` reuses that payload on the client with zero refetch
|
|
83
85
|
- Tree-shakable: `sideEffects: false` — unused components and hooks drop out of the bundle
|
|
84
86
|
|
|
@@ -252,6 +254,50 @@ function ArticleList() {
|
|
|
252
254
|
|
|
253
255
|
`useSearch()` without a schema degrades to the raw input object of `parseSearchInput` (strings; repeated keys are arrays) and needs no schema on the route. Both flavors re-render on every location change, and the schema must validate synchronously.
|
|
254
256
|
|
|
257
|
+
Write the search through the same schema — `useSetSearch(schema)` validates the next value before any navigation, throws `SearchError` (with the schema's issues) without touching the location when it rejects, and writes the schema's own output so defaults apply:
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
import {useSearch, useSetSearch} from '@native-router/react';
|
|
261
|
+
|
|
262
|
+
function Pager() {
|
|
263
|
+
const {page} = useSearch(listSearch);
|
|
264
|
+
const setSearch = useSetSearch(listSearch);
|
|
265
|
+
|
|
266
|
+
function go(next: number) {
|
|
267
|
+
setSearch({page: String(next)}); // push; {replace: true} rewrites
|
|
268
|
+
}
|
|
269
|
+
// ...
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Make `Link` targets type-safe: build the table with `createRoutes` (a `satisfies`-style identity function that keeps every `path` literal), extract the pattern union with `RoutePaths`, and narrow `TypedLink` to it. `params` is checked against the exact pattern's param segments — `:name` wants a string, `*name` a string array:
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import {TypedLink, createRoutes} from '@native-router/react';
|
|
277
|
+
import type {RoutePaths} from '@native-router/react';
|
|
278
|
+
|
|
279
|
+
const routes = createRoutes({
|
|
280
|
+
component: () => import('./Layout'),
|
|
281
|
+
children: [
|
|
282
|
+
{path: '/', component: () => import('./Home')},
|
|
283
|
+
{path: '/users/:id', component: () => import('./UserProfile')},
|
|
284
|
+
{path: '/files/*rest', component: () => import('./Files')}
|
|
285
|
+
]
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
type AppPaths = RoutePaths<typeof routes>; // '/' | '/users/:id' | '/files/*rest'
|
|
289
|
+
|
|
290
|
+
<TypedLink<AppPaths> to="/users/:id" params={{id: '7'}}>User 7</TypedLink>
|
|
291
|
+
// @ts-expect-error '/help' is not a pattern of the table
|
|
292
|
+
<TypedLink<AppPaths> to="/help">Help</TypedLink>
|
|
293
|
+
// @ts-expect-error params are required for '/users/:id'
|
|
294
|
+
<TypedLink<AppPaths> to="/users/:id">User ?</TypedLink>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
At click time the params are interpolated into the pattern (values percent-encoded, wildcard segments joined with `/`); a missing required param throws instead of navigating — the runtime backstop of the type-level check. An `as Route` assertion widens every `path` to `string`, so `RoutePaths` degrades to `string` and `TypedLink` accepts any path, exactly like a plain `Link` — the migration is opt-in.
|
|
298
|
+
|
|
299
|
+
A render error never crashes past its route: the level's resolved view is wrapped in a route-level error boundary, which renders the same route `errorComponent` with `ctx.phase === 'render'` (the resolve-phase fallback passes no `phase`). Without a route `errorComponent` the error goes to the global `errorHandler`; the boundary keeps working across recoveries — a retry button can `refresh(router)`, and navigating away renders the next view normally.
|
|
300
|
+
|
|
255
301
|
See [demos](./demos) for a complete example.
|
|
256
302
|
|
|
257
303
|
## Development
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-C-Bgsx5W.cjs");let t=require("react"),r=require("history"),n=require("react/jsx-runtime"),o=require("@native-router/core"),s=require("use-sync-external-store/shim");function c(e,t,r){return!(e.defaultPrevented||0!==e.button||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||"_blank"===t||"_parent"===t||"_top"===t||(r??"").split(/\s+/).includes("external"))}function a({to:r,...s}){const a=e.useRouter(),u=(0,t.useRef)(!1);return(0,n.jsx)("a",{...s,href:(0,o.createHref)(a,r),onClick:function(e){c(e,s.target,s.rel)&&(e.preventDefault(),u.current||(u.current=!0,(0,o.navigate)(a,r).catch(()=>{}).finally(()=>{u.current=!1})))}})}var u=(0,t.createContext)({loading:!1});function i(e,t){return e.replace(/\\.|[:*]([A-Za-z_$][A-Za-z0-9_$]*)/g,(r,n)=>{if(void 0===n)return r;const o=t[n];if(void 0===o||0===o.length)throw new Error(`Missing param "${n}" for the path pattern "${e}"`);return(Array.isArray(o)?o:[o]).map(encodeURIComponent).join("/")})}var l=function({to:r,params:s,onClick:a,...u}){const l=e.useRouter(),f=(0,t.useRef)(!1);let h=r;try{h=i(r,s??{})}catch{}return(0,n.jsx)("a",{...u,href:(0,o.createHref)(l,h),onClick:function(e){if(a?.(e),!c(e,u.target,u.rel))return;if(e.preventDefault(),f.current)return;const t=i(r,s??{});f.current=!0,(0,o.navigate)(l,t).catch(()=>{}).finally(()=>{f.current=!1})}})};function f(e){return Object.entries(e).filter(([,e])=>null!=e).map(([e,t])=>(Array.isArray(t)?t:[t]).map(t=>`${encodeURIComponent(e)}=${encodeURIComponent(String(t))}`).join("&")).join("&")}function h(e,t,r){const{history:n}=e,{pathname:s,hash:c}=n.location,a=s+(t?`?${t}`:"")+c;return r?.replace?(0,o.resolveEntry)(e,(0,o.toLocation)(e,a)).then(t=>(0,o.commitReplace)(e,t.task,t.location)):(0,o.navigate)(e,a)}exports.HashRouter=e.HashRouter,exports.HistoryRouter=e.HistoryRouter,exports.Link=a,exports.MemoryRouter=e.MemoryRouter,exports.NavLink=function({to:r,end:c=!1,caseSensitive:u=!1,className:i,style:l,ariaCurrent:f,children:h,...p}){const y=e.useRouter(),d=(0,t.useCallback)(e=>y.history.listen(()=>e()),[y]),x=(0,t.useCallback)(()=>y.history.location.pathname,[y]),R=(0,s.useSyncExternalStore)(d,x,x),v=(0,o.toLocation)(y,r).pathname,[m,w]=u?[R,v]:[R.toLowerCase(),v.toLowerCase()],S=m===w,k=c||S?S:m.startsWith(w.endsWith("/")?w:`${w}/`),C={isActive:k,isExactActive:S};return(0,n.jsx)(a,{to:r,...p,className:"function"==typeof i?i(C):i,style:"function"==typeof l?l(C):l,"aria-current":k?f??"page":void 0,children:"function"==typeof h?h(C):h})},exports.PrefetchLink=function({to:r,prefetch:s="intent",children:a,...i}){const l=e.useRouter(),f=(0,t.useRef)(null),h=(0,t.useRef)(void 0),p=(0,t.useRef)(!1),[y,d]=(0,t.useState)(!1),[x,R]=(0,t.useState)(),[v,m]=(0,t.useState)();function w(){d(!0),R(void 0),p.current=!1;const e=(0,o.preload)(l,r);return h.current=e,e.then(e=>e.task).then(e=>m(e),e=>{R(e),p.current=!0}).finally(()=>d(!1)),e}function S(){h.current||w()}(0,t.useEffect)(()=>{h.current=void 0,p.current=!1,d(!1),R(void 0),m(void 0)},[r,l]),(0,t.useEffect)(()=>{if("render"===s)return void S();if("viewport"!==s)return;const e=f.current;if(!e||"undefined"==typeof IntersectionObserver)return;const t=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(t.disconnect(),S())});return t.observe(e),()=>t.disconnect()},[s,r,l]);const k=(0,t.useMemo)(()=>({loading:y,error:x,view:v}),[y,x,v]),C="intent"===s?{onMouseEnter:S,onFocus:S}:void 0;return(0,n.jsx)(u.Provider,{value:k,children:(0,n.jsx)("a",{...i,...C,ref:f,href:(0,o.createHref)(l,r),onClick:function(e){if(!c(e,i.target,i.rel))return;e.preventDefault();const t=h.current;(!t||p.current?w():t).then(e=>(0,o.commit)(l,e.task,e.location)).catch(()=>{}).finally(()=>{h.current=void 0,p.current=!1})},children:a})})},exports.Router=e.Router,exports.ScrollRestoration=function({resetOnPush:n=!0}){const o=e.useRouter(),s=(0,t.useRef)(new Map),c=(0,t.useRef)(-1),a=(0,t.useRef)("");return(0,t.useEffect)(()=>{if("undefined"==typeof window)return;window.history.scrollRestoration&&(window.history.scrollRestoration="manual");const e=s.current,t=e=>e?.index||0;c.current=t(o.history.location.state),a.current=(0,r.createPath)(o.history.location);let u=!1,i=!1,l=!0;const f=o.history.listen(({action:s})=>{i||="POP"===s,u||(u=!0,queueMicrotask(()=>{if(u=!1,!l)return;const s=i;i=!1;const{location:f}=o.history,h=t(f.state),p=(0,r.createPath)(f),y=c.current;if(h!==y&&e.set(y,{x:window.scrollX,y:window.scrollY}),s){const t=e.get(h)??{x:0,y:0};window.scrollTo(t.x,t.y),e.set(h,t)}else!n||h===y&&p===a.current||(window.scrollTo(0,0),e.set(h,{x:0,y:0}));c.current=h,a.current=p}))});return()=>{l=!1,f()}},[o,n]),null},exports.TypedLink=l,exports.View=e.View,exports.createRouter=e.createRouter,exports.createRoutes=function(e){return e},exports.defaultResolveView=e.resolveView,exports.hydrate=e.hydrate,exports.useData=e.useData,exports.useLoading=e.useLoading,exports.useMatched=e.useMatched,exports.useNamedData=e.useNamedData,exports.usePrefetch=function(){return(0,t.useContext)(u)},exports.useRouter=e.useRouter,exports.useSearch=function(r){const n=e.useRouter(),c=(0,t.useCallback)(e=>n.history.listen(()=>e()),[n]),a=(0,t.useCallback)(()=>n.history.location.search,[n]),u=(0,t.useCallback)(()=>"",[]),i=(0,s.useSyncExternalStore)(c,a,u);return r?(0,o.parseSearchSync)(r,i):(0,o.parseSearchInput)(i)},exports.useSearchParams=function(){const r=e.useRouter(),n=(0,t.useCallback)(e=>r.history.listen(()=>e()),[r]),o=(0,t.useCallback)(()=>r.history.location.search,[r]),c=(0,t.useCallback)(()=>"",[]);return[new URLSearchParams((0,s.useSyncExternalStore)(n,o,c)),(0,t.useCallback)((e,t)=>{const{history:n}=r,o="function"==typeof e?e(new URLSearchParams(n.location.search)):e;return h(r,o.toString(),t)},[r])]},exports.useSetSearch=function(r){const n=e.useRouter();return(0,t.useCallback)((e,t)=>{const{history:s}=n,c="function"==typeof e?e((0,o.parseSearchInput)(s.location.search)):e,a=(0,o.parseSearchSync)(r,f(c));return h(n,f(a),t)},[n,r])},exports.useView=e.useView;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/components/link-behavior.ts","../src/components/Link.tsx","../src/components/NavLink.tsx","../src/components/PrefetchLink.tsx","../src/components/ScrollRestoration.tsx","../src/use-search-params.ts"],"sourcesContent":["/**\n * Checks whether a click on an anchor should be intercepted and handled as an\n * in-app navigation, following the standard link interception semantics of\n * react-router/wouter: plain left clicks only.\n *\n * The browser keeps the default behavior whenever this returns `false`, so\n * modified clicks, middle clicks and links to other browsing contexts open in\n * a new tab/window as the user expects.\n *\n * @param e The click event to inspect.\n * @param target The anchor's `target` attribute, if any.\n * @param rel The anchor's `rel` attribute, if any.\n * @group Components\n */\n\nexport function shouldNavigate(\n e: {\n button: number;\n defaultPrevented: boolean;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n },\n target?: string,\n rel?: string\n): boolean {\n return (\n !e.defaultPrevented &&\n e.button === 0 &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey &&\n target !== '_blank' &&\n target !== '_parent' &&\n target !== '_top' &&\n !(rel ?? '').split(/\\s+/).includes('external')\n );\n}\n","import {createHref, navigate} from '@native-router/core';\nimport type {LinkProps} from '@@/types';\nimport {useRef, type MouseEvent} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\n/**\n * Link for navigate in app.\n * @param props\n * @group Components\n */\nexport default function Link({to, ...rest}: LinkProps) {\n const router = useRouter();\n const lockRef = useRef(false);\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n\n if (lockRef.current) return;\n lockRef.current = true;\n navigate(router, to)\n .catch(() => undefined)\n .finally(() => {\n lockRef.current = false;\n });\n }\n return (\n // eslint-disable-next-line jsx-a11y/anchor-has-content\n <a {...rest} href={createHref(router, to)} onClick={handleClick} />\n );\n}\n","import {toLocation} from '@native-router/core';\nimport type {NavLinkProps, NavLinkState} from '@@/types';\nimport {useCallback} from 'react';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './Router';\nimport Link from './Link';\n\n/**\n * Link that knows whether its target matches the current location.\n *\n * Active rules(aligned with react-router's `NavLink`):\n *\n * - `target` is the pathname of `toLocation(router, to)`(baseUrl prepended),\n * `current` is `router.history.location.pathname`; both are lowercased\n * unless `caseSensitive` is set.\n * - `isExactActive`: `current === target`.\n * - `isActive`: `isExactActive` when `end` is set, otherwise `current` equals\n * `target` or starts with `target` plus a trailing `/`(so `to=\"/\"` is active\n * for every path).\n *\n * While active the anchor renders `aria-current={ariaCurrent ?? 'page'}` and\n * `className`/`style`/`children` receive the active state when given as\n * functions. Click behavior is delegated to {@link Link}, inheriting the\n * modified-click guard and the double-click lock.\n * @param props\n * @group Components\n */\nexport default function NavLink({\n to,\n end = false,\n caseSensitive = false,\n className,\n style,\n ariaCurrent,\n children,\n ...rest\n}: NavLinkProps) {\n const router = useRouter();\n // Subscribe to the history location so the active state stays in sync even\n // when rendered outside the routed view(e.g. a nav bar beside <View />).\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n const getSnapshot = useCallback(\n () => router.history.location.pathname,\n [router]\n );\n const current = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const target = toLocation(router, to).pathname;\n const [currentPath, targetPath] = caseSensitive\n ? [current, target]\n : [current.toLowerCase(), target.toLowerCase()];\n\n const isExactActive = currentPath === targetPath;\n // A root `to=\"/\"` normalizes to the \"/\" prefix and matches every path.\n const isActive =\n end || isExactActive\n ? isExactActive\n : currentPath.startsWith(\n targetPath.endsWith('/') ? targetPath : `${targetPath}/`\n );\n\n const state: NavLinkState = {isActive, isExactActive};\n\n return (\n <Link\n to={to}\n {...rest}\n className={typeof className === 'function' ? className(state) : className}\n style={typeof style === 'function' ? style(state) : style}\n aria-current={isActive ? (ariaCurrent ?? 'page') : undefined}\n >\n {typeof children === 'function' ? children(state) : children}\n </Link>\n );\n}\n","import {commit, createHref, preload} from '@native-router/core';\nimport type {ResolvedEntry} from '@native-router/core';\nimport type {LinkProps, Route} from '@@/types';\nimport {\n createContext,\n MouseEvent,\n ReactNode,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\ntype PrefetchLinkContext = {loading: boolean; error?: Error; view?: ReactNode};\n\nconst Context = createContext<PrefetchLinkContext>({loading: false});\n\n/**\n * Get the prefetch context. Use for render a preview view.\n * @group Hooks\n */\nexport function usePrefetch() {\n return useContext(Context);\n}\n\n/**\n * Link with prefetch support.\n *\n * The `prefetch` prop controls when the target view is resolved:\n * - `'intent'` (default): prefetch on hover or focus.\n * - `'render'`: prefetch as soon as the link mounts.\n * - `'viewport'`: prefetch when the link scrolls into the viewport.\n * - `'none'`: never prefetch; the target is resolved on click.\n *\n * @param props\n * @group Components\n */\nexport default function PrefetchLink({\n to,\n prefetch = 'intent',\n children,\n ...rest\n}: LinkProps) {\n const router = useRouter();\n const anchorRef = useRef<HTMLAnchorElement>(null);\n const entryRef = useRef<Promise<ResolvedEntry<ReactNode>> | undefined>(\n undefined\n );\n const failedRef = useRef(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error>();\n const [view, setView] = useState<ReactNode>();\n\n function prefetchIt(): Promise<ResolvedEntry<ReactNode>> {\n setLoading(true);\n setError(undefined);\n failedRef.current = false;\n // Route guards(redirect/beforeLoad) run before the view resolves; the\n // stored entry carries the terminal location, so prefetch, preview and\n // commit all agree on the final target. preload() caches the entry at\n // the router level(keyed by pathname+search, TTL bounded), so repeated\n // prefetches of the same target share one resolution and the entry is\n // evicted once committed.\n const entryPromise = preload<Route, ReactNode>(router, to);\n entryRef.current = entryPromise;\n // The derived chain carries the loading/error/view state and handles\n // the rejection of the entry task, so a prefetched task that is never\n // committed does not surface as a global unhandledrejection. Failure is\n // tracked in `failedRef` instead of relying on the rejected task itself.\n entryPromise\n .then((entry) => entry.task)\n .then(\n (v) => setView(v),\n (e) => {\n setError(e);\n failedRef.current = true;\n }\n )\n .finally(() => setLoading(false));\n return entryPromise;\n }\n\n function handlePrefetch() {\n if (entryRef.current) return;\n prefetchIt();\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n // A stored entry that already failed can never be committed\n // successfully, so resolve the target again before committing.\n const stored = entryRef.current;\n const entryPromise = !stored || failedRef.current ? prefetchIt() : stored;\n // The terminal location of the entry is committed, not the link target.\n entryPromise\n .then((entry) => commit(router, entry.task, entry.location))\n .catch(() => undefined)\n .finally(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n });\n }\n\n // Reset every piece of prefetch state when the target changes, so a stale\n // error or preview from the previous target is not rendered for the new one.\n useEffect(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n setLoading(false);\n setError(undefined);\n setView(undefined);\n }, [to, router]);\n\n useEffect(() => {\n if (prefetch === 'render') {\n handlePrefetch();\n return undefined;\n }\n if (prefetch !== 'viewport') return undefined;\n const el = anchorRef.current;\n if (!el || typeof IntersectionObserver === 'undefined') return undefined;\n // eslint-disable-next-line compat/compat -- guarded above at runtime\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return;\n observer.disconnect();\n handlePrefetch();\n });\n observer.observe(el);\n return () => observer.disconnect();\n // `location` and `handlePrefetch` are derived from these deps.\n }, [prefetch, to, router]);\n\n const linkContext = useMemo(\n () => ({loading, error, view}),\n [loading, error, view]\n );\n\n // Hover/focus are intent signals only; 'render', 'viewport' and 'none'\n // never prefetch on interaction.\n const intentHandlers =\n prefetch === 'intent'\n ? {onMouseEnter: handlePrefetch, onFocus: handlePrefetch}\n : undefined;\n\n return (\n <Context.Provider value={linkContext}>\n <a\n {...rest}\n {...intentHandlers}\n ref={anchorRef}\n href={createHref(router, to)}\n onClick={handleClick}\n >\n {children}\n </a>\n </Context.Provider>\n );\n}\n","import {createPath} from 'history';\nimport type {HistoryState} from '@native-router/core';\nimport {useEffect, useRef} from 'react';\nimport {useRouter} from './Router';\n\n/** Saved scroll offset of one history stack slot. */\ntype ScrollPosition = {x: number; y: number};\n\ntype ScrollRestorationProps = {\n /**\n * Scroll a freshly pushed(or replaced) entry back to the top, like a full\n * page load. Set to false to keep the current offset across forward\n * navigations(e.g. feed-style \"load more\" pages). POP always restores the\n * saved offset, regardless of this flag.\n * @default true\n */\n resetOnPush?: boolean;\n};\n\n/**\n * Restore the window scroll position across history navigations.\n *\n * The router restores views from the in-memory `viewStack`: on back/forward\n * the view is reused without re-fetching its data, but the browser has\n * already left the old document position and a remounted DOM starts at the\n * top by default. Mount `<ScrollRestoration />` anywhere inside\n * {@link Router}(typically in the root layout) to fill that gap:\n *\n * - the scroll offset of every visited entry is remembered, keyed by the\n * absolute history index(`history.location.state.index` — the same key\n * `viewStack` is keyed by). Like `viewStack`, the map is in-memory and\n * session scoped: after a page reload there is nothing to restore and the\n * browser's own restoration takes over.\n * - `history.scrollRestoration` is taken over as `manual`(the browser's\n * own `auto` restoration would race the restore and pre-scroll while the\n * left entry's offset is still being read), making the component solely\n * responsible for restoration; the takeover is session scoped and not\n * reverted on unmount, like `viewStack`.\n * - POP restores the saved offset of the landed entry(`0,0` when none was\n * saved, e.g. forward-past-the-end or post-reload entries).\n * - PUSH and REPLACE scroll a fresh entry back to the top when\n * `resetOnPush` is set(default). Internal re-commits of the very same\n * entry(the core listener's POP sync, `refresh`, listen bootstrap) never\n * touch the scroll.\n *\n * Renders nothing. SSR safe: it only subscribes in an effect and only when\n * `window` exists.\n * @param props\n * @group Components\n */\nexport default function ScrollRestoration({\n resetOnPush = true\n}: ScrollRestorationProps) {\n const router = useRouter();\n // Positions survive effect re-runs(resetOnPush changes) on purpose.\n const positionsRef = useRef(new Map<number, ScrollPosition>());\n const lastIndexRef = useRef(-1);\n const lastPathRef = useRef('');\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n // The browser's own `auto` restoration races this component's restore\n // (it can pre-scroll within the microtask window where the left entry's\n // offset is still being read), so take full control for the session.\n if (window.history.scrollRestoration) {\n window.history.scrollRestoration = 'manual';\n }\n\n const positions = positionsRef.current;\n const readIndex = (state: unknown) =>\n (state as HistoryState | undefined)?.index || 0;\n\n lastIndexRef.current = readIndex(router.history.location.state);\n lastPathRef.current = createPath(router.history.location);\n\n let scheduled = false;\n let sawPop = false;\n let active = true;\n\n const unlisten = router.history.listen(({action}) => {\n // The core listener re-commits a landed POP entry with an internal\n // REPLACE(stack serialization sync), and listener registration order\n // decides whether that REPLACE reaches us before or after the POP\n // itself. Collapse the whole synchronous event storm and decide once,\n // from the final history state, in a microtask — no layout has\n // scrolled yet at that point, so saving the left entry's offset\n // still reads the pre-navigation scroll.\n sawPop ||= action === 'POP';\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n if (!active) return;\n const popped = sawPop;\n sawPop = false;\n const {location} = router.history;\n const index = readIndex(location.state);\n const path = createPath(location);\n const lastIndex = lastIndexRef.current;\n\n if (index !== lastIndex) {\n // The entry we just left keeps its scroll offset.\n positions.set(lastIndex, {x: window.scrollX, y: window.scrollY});\n }\n\n if (popped) {\n const saved = positions.get(index) ?? {x: 0, y: 0};\n window.scrollTo(saved.x, saved.y);\n positions.set(index, saved);\n } else if (\n resetOnPush &&\n (index !== lastIndex || path !== lastPathRef.current)\n ) {\n // A fresh forward entry, or the current entry rewritten to a\n // different location(a replace navigation) — start at the top.\n window.scrollTo(0, 0);\n positions.set(index, {x: 0, y: 0});\n }\n // Anything else is an internal re-commit of the same entry: keep\n // the current scroll.\n\n lastIndexRef.current = index;\n lastPathRef.current = path;\n });\n });\n\n return () => {\n active = false;\n unlisten();\n };\n }, [router, resetOnPush]);\n\n return null;\n}\n","import {useCallback} from 'react';\nimport {\n commitReplace,\n navigate,\n parseSearchInput,\n parseSearchSync,\n resolveEntry,\n toLocation\n} from '@native-router/core';\nimport type {\n SearchInput,\n SearchOutputOf,\n StandardSchemaV1\n} from '@native-router/core';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './components/Router';\n\ntype SetSearchParams = (\n next: URLSearchParams | ((prev: URLSearchParams) => URLSearchParams),\n opts?: {replace?: boolean}\n) => Promise<void> | void;\n\n/**\n * Read and write the search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as the Router Component), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; a fresh `URLSearchParams` view is derived from that string\n * on each render.\n *\n * The setter navigates like any other route change: by default the new\n * search is pushed onto the history stack(mainstream react-router\n * semantics), pass `{replace: true}` to rewrite the current entry instead.\n * Since the search is part of the location, every write re-resolves the\n * matched route, so route `data` fetchers(see {@link useData}) observe the\n * new search on the next {@link useData} read.\n *\n * @group Hooks\n * @returns [searchParams, setSearchParams] - the current search params and\n * the setter(functional updates receive the live previous params); the\n * setter returns a `Promise<void>` that resolves once the navigation\n * commits, so callers may optionally `await` it\n */\nexport function useSearchParams(): [URLSearchParams, SetSearchParams] {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a URLSearchParams instance) to keep the\n // snapshot reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n const searchParams = new URLSearchParams(\n useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n );\n\n const setSearchParams = useCallback<SetSearchParams>(\n (next, opts) => {\n const {history} = router;\n const params =\n typeof next === 'function'\n ? // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n next(new URLSearchParams(history.location.search))\n : next;\n const qs = params.toString();\n const {pathname, hash} = history.location;\n const to = pathname + (qs ? `?${qs}` : '') + hash;\n if (opts?.replace) {\n // Route guards run like any other navigation(align with the push\n // branch): the entry carries the terminal location, so a redirect\n // replaces the current entry with its final target.\n return resolveEntry(router, toLocation(router, to)).then((entry) =>\n commitReplace(router, entry.task, entry.location)\n );\n }\n return navigate(router, to);\n },\n [router]\n );\n\n return [searchParams, setSearchParams];\n}\n\n/**\n * Read the parsed search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as {@link useSearchParams}), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; the parse itself runs on each render.\n *\n * Without a schema the hook degrades to the raw input object of\n * `parseSearchInput` — strings, arrays for repeated keys\n * (`{page: '2', tag: ['a', 'b']}`). With a schema — any zod/valibot/\n * arktype schema, see the route {@link Route.search search field} — the\n * returned object is the schema's parsed output, so coercion and defaults\n * apply, e.g. `useSearch(pageSchema).page` is a number.\n *\n * Prefer declaring the schema once on the route: its `data` loader then\n * receives a parsed `ctx.search` during resolve, and an invalid search\n * fails the navigation through the existing error channels instead of\n * throwing during render.\n *\n * @group Hooks\n * @param schema an optional Standard Schema validator of the search; it\n * must validate synchronously\n * @returns the parsed search params of the current location\n * @throws {SearchError} when `schema` rejects the current search\n */\nexport function useSearch<S extends StandardSchemaV1>(\n schema: S\n): SearchOutputOf<S>;\nexport function useSearch(): SearchInput;\nexport function useSearch(schema?: StandardSchemaV1): unknown {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a parsed object) to keep the snapshot\n // reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n const search = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getServerSnapshot\n );\n\n return schema ? parseSearchSync(schema, search) : parseSearchInput(search);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/components/link-behavior.ts","../src/components/Link.tsx","../src/components/NavLink.tsx","../src/components/PrefetchLink.tsx","../src/components/ScrollRestoration.tsx","../src/components/TypedLink.tsx","../src/create-routes.js","../src/use-search-params.ts"],"sourcesContent":["/**\n * Checks whether a click on an anchor should be intercepted and handled as an\n * in-app navigation, following the standard link interception semantics of\n * react-router/wouter: plain left clicks only.\n *\n * The browser keeps the default behavior whenever this returns `false`, so\n * modified clicks, middle clicks and links to other browsing contexts open in\n * a new tab/window as the user expects.\n *\n * @param e The click event to inspect.\n * @param target The anchor's `target` attribute, if any.\n * @param rel The anchor's `rel` attribute, if any.\n * @group Components\n */\n\nexport function shouldNavigate(\n e: {\n button: number;\n defaultPrevented: boolean;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n },\n target?: string,\n rel?: string\n): boolean {\n return (\n !e.defaultPrevented &&\n e.button === 0 &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey &&\n target !== '_blank' &&\n target !== '_parent' &&\n target !== '_top' &&\n !(rel ?? '').split(/\\s+/).includes('external')\n );\n}\n","import {createHref, navigate} from '@native-router/core';\nimport type {LinkProps} from '@@/types';\nimport {useRef, type MouseEvent} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\n/**\n * Link for navigate in app.\n * @param props\n * @group Components\n */\nexport default function Link({to, ...rest}: LinkProps) {\n const router = useRouter();\n const lockRef = useRef(false);\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n\n if (lockRef.current) return;\n lockRef.current = true;\n navigate(router, to)\n .catch(() => undefined)\n .finally(() => {\n lockRef.current = false;\n });\n }\n return (\n // eslint-disable-next-line jsx-a11y/anchor-has-content\n <a {...rest} href={createHref(router, to)} onClick={handleClick} />\n );\n}\n","import {toLocation} from '@native-router/core';\nimport type {NavLinkProps, NavLinkState} from '@@/types';\nimport {useCallback} from 'react';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './Router';\nimport Link from './Link';\n\n/**\n * Link that knows whether its target matches the current location.\n *\n * Active rules(aligned with react-router's `NavLink`):\n *\n * - `target` is the pathname of `toLocation(router, to)`(baseUrl prepended),\n * `current` is `router.history.location.pathname`; both are lowercased\n * unless `caseSensitive` is set.\n * - `isExactActive`: `current === target`.\n * - `isActive`: `isExactActive` when `end` is set, otherwise `current` equals\n * `target` or starts with `target` plus a trailing `/`(so `to=\"/\"` is active\n * for every path).\n *\n * While active the anchor renders `aria-current={ariaCurrent ?? 'page'}` and\n * `className`/`style`/`children` receive the active state when given as\n * functions. Click behavior is delegated to {@link Link}, inheriting the\n * modified-click guard and the double-click lock.\n * @param props\n * @group Components\n */\nexport default function NavLink({\n to,\n end = false,\n caseSensitive = false,\n className,\n style,\n ariaCurrent,\n children,\n ...rest\n}: NavLinkProps) {\n const router = useRouter();\n // Subscribe to the history location so the active state stays in sync even\n // when rendered outside the routed view(e.g. a nav bar beside <View />).\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n const getSnapshot = useCallback(\n () => router.history.location.pathname,\n [router]\n );\n const current = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const target = toLocation(router, to).pathname;\n const [currentPath, targetPath] = caseSensitive\n ? [current, target]\n : [current.toLowerCase(), target.toLowerCase()];\n\n const isExactActive = currentPath === targetPath;\n // A root `to=\"/\"` normalizes to the \"/\" prefix and matches every path.\n const isActive =\n end || isExactActive\n ? isExactActive\n : currentPath.startsWith(\n targetPath.endsWith('/') ? targetPath : `${targetPath}/`\n );\n\n const state: NavLinkState = {isActive, isExactActive};\n\n return (\n <Link\n to={to}\n {...rest}\n className={typeof className === 'function' ? className(state) : className}\n style={typeof style === 'function' ? style(state) : style}\n aria-current={isActive ? (ariaCurrent ?? 'page') : undefined}\n >\n {typeof children === 'function' ? children(state) : children}\n </Link>\n );\n}\n","import {commit, createHref, preload} from '@native-router/core';\nimport type {ResolvedEntry} from '@native-router/core';\nimport type {LinkProps, Route} from '@@/types';\nimport {\n createContext,\n MouseEvent,\n ReactNode,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\ntype PrefetchLinkContext = {loading: boolean; error?: Error; view?: ReactNode};\n\nconst Context = createContext<PrefetchLinkContext>({loading: false});\n\n/**\n * Get the prefetch context. Use for render a preview view.\n * @group Hooks\n */\nexport function usePrefetch() {\n return useContext(Context);\n}\n\n/**\n * Link with prefetch support.\n *\n * The `prefetch` prop controls when the target view is resolved:\n * - `'intent'` (default): prefetch on hover or focus.\n * - `'render'`: prefetch as soon as the link mounts.\n * - `'viewport'`: prefetch when the link scrolls into the viewport.\n * - `'none'`: never prefetch; the target is resolved on click.\n *\n * @param props\n * @group Components\n */\nexport default function PrefetchLink({\n to,\n prefetch = 'intent',\n children,\n ...rest\n}: LinkProps) {\n const router = useRouter();\n const anchorRef = useRef<HTMLAnchorElement>(null);\n const entryRef = useRef<Promise<ResolvedEntry<ReactNode>> | undefined>(\n undefined\n );\n const failedRef = useRef(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error>();\n const [view, setView] = useState<ReactNode>();\n\n function prefetchIt(): Promise<ResolvedEntry<ReactNode>> {\n setLoading(true);\n setError(undefined);\n failedRef.current = false;\n // Route guards(redirect/beforeLoad) run before the view resolves; the\n // stored entry carries the terminal location, so prefetch, preview and\n // commit all agree on the final target. preload() caches the entry at\n // the router level(keyed by pathname+search, TTL bounded), so repeated\n // prefetches of the same target share one resolution and the entry is\n // evicted once committed.\n const entryPromise = preload<Route, ReactNode>(router, to);\n entryRef.current = entryPromise;\n // The derived chain carries the loading/error/view state and handles\n // the rejection of the entry task, so a prefetched task that is never\n // committed does not surface as a global unhandledrejection. Failure is\n // tracked in `failedRef` instead of relying on the rejected task itself.\n entryPromise\n .then((entry) => entry.task)\n .then(\n (v) => setView(v),\n (e) => {\n setError(e);\n failedRef.current = true;\n }\n )\n .finally(() => setLoading(false));\n return entryPromise;\n }\n\n function handlePrefetch() {\n if (entryRef.current) return;\n prefetchIt();\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n // A stored entry that already failed can never be committed\n // successfully, so resolve the target again before committing.\n const stored = entryRef.current;\n const entryPromise = !stored || failedRef.current ? prefetchIt() : stored;\n // The terminal location of the entry is committed, not the link target.\n entryPromise\n .then((entry) => commit(router, entry.task, entry.location))\n .catch(() => undefined)\n .finally(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n });\n }\n\n // Reset every piece of prefetch state when the target changes, so a stale\n // error or preview from the previous target is not rendered for the new one.\n useEffect(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n setLoading(false);\n setError(undefined);\n setView(undefined);\n }, [to, router]);\n\n useEffect(() => {\n if (prefetch === 'render') {\n handlePrefetch();\n return undefined;\n }\n if (prefetch !== 'viewport') return undefined;\n const el = anchorRef.current;\n if (!el || typeof IntersectionObserver === 'undefined') return undefined;\n // eslint-disable-next-line compat/compat -- guarded above at runtime\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return;\n observer.disconnect();\n handlePrefetch();\n });\n observer.observe(el);\n return () => observer.disconnect();\n // `location` and `handlePrefetch` are derived from these deps.\n }, [prefetch, to, router]);\n\n const linkContext = useMemo(\n () => ({loading, error, view}),\n [loading, error, view]\n );\n\n // Hover/focus are intent signals only; 'render', 'viewport' and 'none'\n // never prefetch on interaction.\n const intentHandlers =\n prefetch === 'intent'\n ? {onMouseEnter: handlePrefetch, onFocus: handlePrefetch}\n : undefined;\n\n return (\n <Context.Provider value={linkContext}>\n <a\n {...rest}\n {...intentHandlers}\n ref={anchorRef}\n href={createHref(router, to)}\n onClick={handleClick}\n >\n {children}\n </a>\n </Context.Provider>\n );\n}\n","import {createPath} from 'history';\nimport type {HistoryState} from '@native-router/core';\nimport {useEffect, useRef} from 'react';\nimport {useRouter} from './Router';\n\n/** Saved scroll offset of one history stack slot. */\ntype ScrollPosition = {x: number; y: number};\n\ntype ScrollRestorationProps = {\n /**\n * Scroll a freshly pushed(or replaced) entry back to the top, like a full\n * page load. Set to false to keep the current offset across forward\n * navigations(e.g. feed-style \"load more\" pages). POP always restores the\n * saved offset, regardless of this flag.\n * @default true\n */\n resetOnPush?: boolean;\n};\n\n/**\n * Restore the window scroll position across history navigations.\n *\n * The router restores views from the in-memory `viewStack`: on back/forward\n * the view is reused without re-fetching its data, but the browser has\n * already left the old document position and a remounted DOM starts at the\n * top by default. Mount `<ScrollRestoration />` anywhere inside\n * {@link Router}(typically in the root layout) to fill that gap:\n *\n * - the scroll offset of every visited entry is remembered, keyed by the\n * absolute history index(`history.location.state.index` — the same key\n * `viewStack` is keyed by). Like `viewStack`, the map is in-memory and\n * session scoped: after a page reload there is nothing to restore and the\n * browser's own restoration takes over.\n * - `history.scrollRestoration` is taken over as `manual`(the browser's\n * own `auto` restoration would race the restore and pre-scroll while the\n * left entry's offset is still being read), making the component solely\n * responsible for restoration; the takeover is session scoped and not\n * reverted on unmount, like `viewStack`.\n * - POP restores the saved offset of the landed entry(`0,0` when none was\n * saved, e.g. forward-past-the-end or post-reload entries).\n * - PUSH and REPLACE scroll a fresh entry back to the top when\n * `resetOnPush` is set(default). Internal re-commits of the very same\n * entry(the core listener's POP sync, `refresh`, listen bootstrap) never\n * touch the scroll.\n *\n * Renders nothing. SSR safe: it only subscribes in an effect and only when\n * `window` exists.\n * @param props\n * @group Components\n */\nexport default function ScrollRestoration({\n resetOnPush = true\n}: ScrollRestorationProps) {\n const router = useRouter();\n // Positions survive effect re-runs(resetOnPush changes) on purpose.\n const positionsRef = useRef(new Map<number, ScrollPosition>());\n const lastIndexRef = useRef(-1);\n const lastPathRef = useRef('');\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n // The browser's own `auto` restoration races this component's restore\n // (it can pre-scroll within the microtask window where the left entry's\n // offset is still being read), so take full control for the session.\n if (window.history.scrollRestoration) {\n window.history.scrollRestoration = 'manual';\n }\n\n const positions = positionsRef.current;\n const readIndex = (state: unknown) =>\n (state as HistoryState | undefined)?.index || 0;\n\n lastIndexRef.current = readIndex(router.history.location.state);\n lastPathRef.current = createPath(router.history.location);\n\n let scheduled = false;\n let sawPop = false;\n let active = true;\n\n const unlisten = router.history.listen(({action}) => {\n // The core listener re-commits a landed POP entry with an internal\n // REPLACE(stack serialization sync), and listener registration order\n // decides whether that REPLACE reaches us before or after the POP\n // itself. Collapse the whole synchronous event storm and decide once,\n // from the final history state, in a microtask — no layout has\n // scrolled yet at that point, so saving the left entry's offset\n // still reads the pre-navigation scroll.\n sawPop ||= action === 'POP';\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n if (!active) return;\n const popped = sawPop;\n sawPop = false;\n const {location} = router.history;\n const index = readIndex(location.state);\n const path = createPath(location);\n const lastIndex = lastIndexRef.current;\n\n if (index !== lastIndex) {\n // The entry we just left keeps its scroll offset.\n positions.set(lastIndex, {x: window.scrollX, y: window.scrollY});\n }\n\n if (popped) {\n const saved = positions.get(index) ?? {x: 0, y: 0};\n window.scrollTo(saved.x, saved.y);\n positions.set(index, saved);\n } else if (\n resetOnPush &&\n (index !== lastIndex || path !== lastPathRef.current)\n ) {\n // A fresh forward entry, or the current entry rewritten to a\n // different location(a replace navigation) — start at the top.\n window.scrollTo(0, 0);\n positions.set(index, {x: 0, y: 0});\n }\n // Anything else is an internal re-commit of the same entry: keep\n // the current scroll.\n\n lastIndexRef.current = index;\n lastPathRef.current = path;\n });\n });\n\n return () => {\n active = false;\n unlisten();\n };\n }, [router, resetOnPush]);\n\n return null;\n}\n","import {createHref, navigate} from '@native-router/core';\nimport {useRef, type MouseEvent, type ReactElement} from 'react';\nimport type {TypedLinkProps} from '@@/types';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\n/**\n * Interpolate params into a path pattern: `:name` segments take a\n * string, `*name` wildcards a string array(joined with `/`), both\n * percent-encoded; everything else — including `\\` escapes — is static\n * text. The grammar matches the core matcher's(the ASCII identifier\n * scanner the type-level `ExtractPathParams` models).\n * @throws when a required param is missing or empty — the click-time\n * params check of {@link TypedLink}\n */\nfunction interpolatePath(\n pattern: string,\n params: Record<string, string | string[]>\n): string {\n return pattern.replace(\n /\\\\.|[:*]([A-Za-z_$][A-Za-z0-9_$]*)/g,\n (match, name?: string) => {\n if (name === undefined) return match;\n const value = params[name];\n if (value === undefined || value.length === 0) {\n throw new Error(\n `Missing param \"${name}\" for the path pattern \"${pattern}\"`\n );\n }\n return (Array.isArray(value) ? value : [value])\n .map(encodeURIComponent)\n .join('/');\n }\n );\n}\n\n/**\n * Link whose `to` is narrowed to a route table's path patterns and whose\n * `params` is checked against the exact pattern's param segments. Give\n * it the pattern union as its type argument:\n *\n * ```tsx\n * const routes = createRoutes({children: [{path: '/users/:id'}, ...]});\n *\n * <TypedLink<RoutePaths<typeof routes>> to=\"/users/:id\" params={{id: '7'}}>\n * User 7\n * </TypedLink>\n * ```\n *\n * A `to` outside the table, a missing required param or a wrong param\n * shape is a compile error; at click time the params are interpolated\n * into the pattern and a missing required param throws instead of\n * navigating(the type-level check's runtime backstop).\n *\n * Without the type argument the component degrades to a plain `Link`:\n * any path, params optional. Click interception follows {@link Link}\n * — only plain primary-button clicks are intercepted.\n * @group Components\n * @param props `to`(a pattern of the table), `params`(per the pattern)\n * and the usual anchor attributes\n */\n// The implementation works on the loose shape; the typed signature is\n// attached below so discriminated-union props need not be destructured\n// across the union.\nfunction TypedLinkImpl({\n to,\n params,\n onClick,\n ...rest\n}: {\n to: string;\n params?: Record<string, string | string[]>;\n} & Omit<TypedLinkProps, 'to' | 'params' | 'prefetch' | 'href'>) {\n const router = useRouter();\n const lockRef = useRef(false);\n\n // The href shows the interpolated target; when a required param is\n // missing the raw pattern stays and the click-time check below blocks\n // the navigation.\n let href: string = to;\n try {\n href = interpolatePath(to, params ?? {});\n } catch {\n // Programming error the types already flag; surfaced on click.\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n onClick?.(e);\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n\n if (lockRef.current) return;\n // Click-time params check per the pattern's param segments: a missing\n // required param throws instead of navigating.\n const target = interpolatePath(to, params ?? {});\n lockRef.current = true;\n navigate(router, target)\n .catch(() => undefined)\n .finally(() => {\n lockRef.current = false;\n });\n }\n\n return (\n // eslint-disable-next-line jsx-a11y/anchor-has-content\n <a {...rest} href={createHref(router, href)} onClick={handleClick} />\n );\n}\n\nconst TypedLink = TypedLinkImpl as <Paths extends string = string>(\n props: TypedLinkProps<Paths>\n) => ReactElement | null;\n\nexport default TypedLink;\n","/**\n * Identity function with `satisfies` semantics: the table is checked\n * against `Route` while every `path` keeps its string-literal type, so\n * `RoutePaths<typeof routes>` can extract the full pattern union for\n * `TypedLink`. An `as Route` assertion does the opposite — it widens\n * every `path` to `string` and gives up the literals.\n *\n * ```tsx\n * const routes = createRoutes({\n * children: [\n * {path: '/', component: () => import('./Home')},\n * {path: '/users/:id', component: () => import('./UserProfile')}\n * ]\n * });\n * // type AppPaths = '/' | '/users/:id'\n * type AppPaths = RoutePaths<typeof routes>;\n * ```\n *\n * Zero runtime cost: the function returns its argument unchanged and\n * tree-shakes away.\n * @group Methods\n * @category Route\n * @param routes the route table, a route object or an array of them\n * @returns the very same route table, literal types preserved\n */\nexport function createRoutes(\n // The `const` modifier keeps every `path` a string literal(through\n // arbitrary nesting) while the parameter is still checked against\n // `Route` — the `satisfies` semantics an `as Route` assertion lacks:\n // the assertion widens every `path` to `string` instead.\n routes\n) {\n return routes;\n}\n","import {useCallback} from 'react';\nimport {\n commitReplace,\n navigate,\n parseSearchInput,\n parseSearchSync,\n resolveEntry,\n toLocation\n} from '@native-router/core';\nimport type {\n SearchInput,\n SearchOutputOf,\n StandardSchemaV1\n} from '@native-router/core';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './components/Router';\n\ntype SetSearchParams = (\n next: URLSearchParams | ((prev: URLSearchParams) => URLSearchParams),\n opts?: {replace?: boolean}\n) => Promise<void> | void;\n\n/**\n * Read and write the search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as the Router Component), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; a fresh `URLSearchParams` view is derived from that string\n * on each render.\n *\n * The setter navigates like any other route change: by default the new\n * search is pushed onto the history stack(mainstream react-router\n * semantics), pass `{replace: true}` to rewrite the current entry instead.\n * Since the search is part of the location, every write re-resolves the\n * matched route, so route `data` fetchers(see {@link useData}) observe the\n * new search on the next {@link useData} read.\n *\n * @group Hooks\n * @returns [searchParams, setSearchParams] - the current search params and\n * the setter(functional updates receive the live previous params); the\n * setter returns a `Promise<void>` that resolves once the navigation\n * commits, so callers may optionally `await` it\n */\nexport function useSearchParams(): [URLSearchParams, SetSearchParams] {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a URLSearchParams instance) to keep the\n // snapshot reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n const searchParams = new URLSearchParams(\n useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n );\n\n const setSearchParams = useCallback<SetSearchParams>(\n (next, opts) => {\n const {history} = router;\n const params =\n typeof next === 'function'\n ? // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n next(new URLSearchParams(history.location.search))\n : next;\n return setSearch(router, params.toString(), opts);\n },\n [router]\n );\n\n return [searchParams, setSearchParams];\n}\n\n/**\n * Write the search params of the current location through a schema, the\n * setter-side twin of `useSearch(schema)`: the next value is serialized\n * to a query string, degraded with `parseSearchInput` and validated by\n * the SAME schema before any navigation happens. A schema that rejects\n * the value throws its issues(`SearchError`) without touching the\n * location; the value the schema would default or coerce on the read\n * side never gets silently written.\n *\n * Synchronous schemas only(the `useSearch` flavor); an async `validate`\n * rejects without navigating.\n *\n * The navigation semantics follow `useSearchParams`' setter: push by\n * default, `{replace: true}` rewrites the current entry, guards run,\n * and the returned `Promise<void>` resolves once the navigation commits.\n *\n * @group Hooks\n * @param schema a Standard Schema validator of the search — must\n * validate synchronously\n * @returns the schema-aware setter; functional updates receive the live\n * previous params\n * @throws {SearchError} when `schema` rejects the next value, before\n * any navigation\n */\nexport function useSetSearch<S extends StandardSchemaV1>(\n schema: S\n): (\n next: SearchInput | ((prev: SearchInput) => SearchInput),\n opts?: {replace?: boolean}\n) => Promise<void> | void {\n const router = useRouter();\n\n return useCallback(\n (next, opts) => {\n const {history} = router;\n const input =\n typeof next === 'function'\n ? next(parseSearchInput(history.location.search))\n : next;\n // Validate the whole next value — not a diff — the same way a\n // navigation to the resulting URL would: parseSearchSync throws\n // SearchError with the schema's issues, and no navigation happens.\n const validated = parseSearchSync(schema, stringifySearch(input));\n // The schema output is authoritative: defaults applied and values\n // coerced to strings, so a partially-filled input still writes the\n // fully-defaulted query.\n return setSearch(\n router,\n stringifySearch(validated as Record<string, unknown>),\n opts\n );\n },\n [router, schema]\n );\n}\n\nfunction stringifySearch(input: Record<string, unknown>): string {\n return Object.entries(input)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) =>\n (Array.isArray(value) ? value : [value])\n .map(\n (v) => `${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`\n )\n .join('&')\n )\n .join('&');\n}\n\nfunction setSearch(\n router: ReturnType<typeof useRouter>,\n qs: string,\n opts?: {replace?: boolean}\n): Promise<void> | void {\n const {history} = router;\n const {pathname, hash} = history.location;\n const to = pathname + (qs ? `?${qs}` : '') + hash;\n if (opts?.replace) {\n // Route guards run like any other navigation(align with the push\n // branch): the entry carries the terminal location, so a redirect\n // replaces the current entry with its final target.\n return resolveEntry(router, toLocation(router, to)).then((entry) =>\n commitReplace(router, entry.task, entry.location)\n );\n }\n return navigate(router, to);\n}\n\n/**\n * Read the parsed search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as {@link useSearchParams}), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; the parse itself runs on each render.\n *\n * Without a schema the hook degrades to the raw input object of\n * `parseSearchInput` — strings, arrays for repeated keys\n * (`{page: '2', tag: ['a', 'b']}`). With a schema — any zod/valibot/\n * arktype schema, see the route {@link Route.search search field} — the\n * returned object is the schema's parsed output, so coercion and defaults\n * apply, e.g. `useSearch(pageSchema).page` is a number.\n *\n * Prefer declaring the schema once on the route: its `data` loader then\n * receives a parsed `ctx.search` during resolve, and an invalid search\n * fails the navigation through the existing error channels instead of\n * throwing during render.\n *\n * @group Hooks\n * @param schema an optional Standard Schema validator of the search; it\n * must validate synchronously\n * @returns the parsed search params of the current location\n * @throws {SearchError} when `schema` rejects the current search\n */\nexport function useSearch<S extends StandardSchemaV1>(\n schema: S\n): SearchOutputOf<S>;\nexport function useSearch(): SearchInput;\nexport function useSearch(schema?: StandardSchemaV1): unknown {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a parsed object) to keep the snapshot\n // reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n const search = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getServerSnapshot\n );\n\n return schema ? parseSearchSync(schema, search) : parseSearchInput(search);\n}\n"],"mappings":"+PAeA,SAAgB,EACd,EAQA,EACA,GAEA,QACG,EAAE,kBACU,IAAb,EAAE,QACD,EAAE,SACF,EAAE,SACF,EAAE,UACF,EAAE,QACQ,WAAX,GACW,YAAX,GACW,SAAX,IACE,GAAO,IAAI,MAAM,OAAO,SAAS,YAEvC,CC5BA,SAAwB,GAAK,GAAC,KAAO,IACnC,MAAM,EAAS,EAAA,YACT,GAAA,EAAU,EAAA,SAAO,GAgBvB,OAEE,EAAA,EAAA,KAAC,IAAD,IAAO,EAAM,MAAA,EAAM,EAAA,YAAW,EAAQ,GAAK,QAhB7C,SAAqB,GAGd,EAAe,EAAG,EAAK,OAAQ,EAAK,OACzC,EAAE,iBAEE,EAAQ,UACZ,EAAQ,SAAU,GAClB,EAAA,EAAA,UAAS,EAAQ,GACd,MAAA,QACA,QAAA,KACC,EAAQ,SAAU,KAExB,GAKF,CEfA,IAAM,GAAA,EAAU,EAAA,eAAmC,CAAC,SAAS,IEH7D,SAAS,EACP,EACA,GAEA,OAAO,EAAQ,QACb,sCAAA,CACC,EAAO,KACN,QAAa,IAAT,EAAoB,OAAO,EAC/B,MAAM,EAAQ,EAAO,GACrB,QAAc,IAAV,GAAwC,IAAjB,EAAM,OAC/B,MAAM,IAAI,MACR,kBAAkB,4BAA+B,MAGrD,OAAQ,MAAM,QAAQ,GAAS,EAAQ,CAAC,IACrC,IAAI,oBACJ,KAAK,MAGd,CA6EA,IAAM,EA/CN,UAAuB,GACrB,EAAA,OACA,EAAA,QACA,KACG,IAKH,MAAM,EAAS,EAAA,YACT,GAAA,EAAU,EAAA,SAAO,GAKvB,IAAI,EAAe,EACnB,IACE,EAAO,EAAgB,EAAI,GAAU,CAAC,EACxC,CAAA,MAEA,CAqBA,OAEE,EAAA,EAAA,KAAC,IAAD,IAAO,EAAM,MAAA,EAAM,EAAA,YAAW,EAAQ,GAAO,QArB/C,SAAqB,GAInB,GAHA,IAAU,IAGL,EAAe,EAAG,EAAK,OAAQ,EAAK,KAAM,OAG/C,GAFA,EAAE,iBAEE,EAAQ,QAAS,OAGrB,MAAM,EAAS,EAAgB,EAAI,GAAU,CAAC,GAC9C,EAAQ,SAAU,GAClB,EAAA,EAAA,UAAS,EAAQ,GACd,MAAA,QACA,QAAA,KACC,EAAQ,SAAU,GAExB,GAMF,EE8BA,SAAS,EAAgB,GACvB,OAAO,OAAO,QAAQ,GACnB,OAAA,EAAQ,CAAG,KAAW,SACtB,IAAA,EAAM,EAAK,MACT,MAAM,QAAQ,GAAS,EAAQ,CAAC,IAC9B,IACE,GAAM,GAAG,mBAAmB,MAAQ,mBAAmB,OAAO,OAEhE,KAAK,MAET,KAAK,IACV,CAEA,SAAS,EACP,EACA,EACA,GAEA,MAAM,QAAC,GAAW,GACZ,SAAC,EAAA,KAAU,GAAQ,EAAQ,SAC3B,EAAK,GAAY,EAAK,IAAI,IAAO,IAAM,EAC7C,OAAI,GAAM,SAIR,EAAO,EAAA,cAAa,GAAA,EAAQ,EAAA,YAAW,EAAQ,IAAK,KAAM,IAAA,EACxD,EAAA,eAAc,EAAQ,EAAM,KAAM,EAAM,YAG5C,EAAO,EAAA,UAAS,EAAQ,EAC1B,0IL9IA,UAAgC,GAC9B,EAAA,IACA,GAAM,EAAA,cACN,GAAgB,EAAA,UAChB,EAAA,MACA,EAAA,YACA,EAAA,SACA,KACG,IAEH,MAAM,EAAS,EAAA,YAGT,GAAA,EAAY,EAAA,aACf,GAA8B,EAAO,QAAQ,OAAA,IAAa,KAC3D,CAAC,IAEG,GAAA,EAAc,EAAA,aAAA,IACZ,EAAO,QAAQ,SAAS,SAC9B,CAAC,IAEG,GAAA,EAAU,EAAA,sBAAqB,EAAW,EAAa,GAEvD,GAAA,EAAS,EAAA,YAAW,EAAQ,GAAI,UAC/B,EAAa,GAAc,EAC9B,CAAC,EAAS,GACV,CAAC,EAAQ,cAAe,EAAO,eAE7B,EAAgB,IAAgB,EAEhC,EACJ,GAAO,EACH,EACA,EAAY,WACV,EAAW,SAAS,KAAO,EAAa,GAAG,MAG7C,EAAsB,CAAC,WAAU,iBAEvC,OACE,EAAA,EAAA,KAAC,EAAD,CACM,QACA,EACJ,UAAgC,mBAAd,EAA2B,EAAU,GAAS,EAChE,MAAwB,mBAAV,EAAuB,EAAM,GAAS,EACpD,eAAc,EAAY,GAAe,YAAU,EAElD,SAAoB,mBAAb,EAA0B,EAAS,GAAS,GAG1D,uBCrCA,UAAqC,GACnC,EAAA,SACA,EAAW,SAAA,SACX,KACG,IAEH,MAAM,EAAS,EAAA,YACT,GAAA,EAAY,EAAA,QAA0B,MACtC,GAAA,EAAW,EAAA,aACf,GAEI,GAAA,EAAY,EAAA,SAAO,IAClB,EAAS,IAAA,EAAc,EAAA,WAAS,IAChC,EAAO,IAAA,EAAY,EAAA,aACnB,EAAM,IAAA,EAAW,EAAA,YAExB,SAAS,IACP,GAAW,GACX,OAAS,GACT,EAAU,SAAU,EAOpB,MAAM,GAAA,EAAe,EAAA,SAA0B,EAAQ,GAgBvD,OAfA,EAAS,QAAU,EAKnB,EACG,KAAM,GAAU,EAAM,MACtB,KACE,GAAM,EAAQ,GACd,IACC,EAAS,GACT,EAAU,SAAU,IAGvB,QAAA,IAAc,GAAW,IACrB,CACT,CAEA,SAAS,IACH,EAAS,SACb,GACF,EAuBA,EAAA,EAAA,WAAA,KACE,EAAS,aAAU,EACnB,EAAU,SAAU,EACpB,GAAW,GACX,OAAS,GACT,OAAQ,IACP,CAAC,EAAI,KAER,EAAA,EAAA,WAAA,KACE,GAAiB,WAAb,EAEF,YADA,IAGF,GAAiB,aAAb,EAAyB,OAC7B,MAAM,EAAK,EAAU,QACrB,IAAK,GAAsC,oBAAzB,qBAAsC,OAExD,MAAM,EAAW,IAAI,qBAAsB,IACpC,EAAQ,KAAM,GAAU,EAAM,kBACnC,EAAS,aACT,OAGF,OADA,EAAS,QAAQ,GACjB,IAAa,EAAS,cAErB,CAAC,EAAU,EAAI,IAElB,MAAM,GAAA,EAAc,EAAA,SAAA,KAAA,CACV,UAAS,QAAO,SACxB,CAAC,EAAS,EAAO,IAKb,EACS,WAAb,EACI,CAAC,aAAc,EAAgB,QAAS,QACxC,EAEN,OACE,EAAA,EAAA,KAAC,EAAQ,SAAT,CAAkB,MAAO,EACvB,UAAA,EAAA,EAAA,KAAC,IAAD,IACM,KACA,EACJ,IAAK,EACL,MAAA,EAAM,EAAA,YAAW,EAAQ,GACzB,QAnEN,SAAqB,GAGnB,IAAK,EAAe,EAAG,EAAK,OAAQ,EAAK,KAAM,OAC/C,EAAE,iBAGF,MAAM,EAAS,EAAS,UACF,GAAU,EAAU,QAAU,IAAe,GAGhE,KAAM,IAAA,EAAU,EAAA,QAAO,EAAQ,EAAM,KAAM,EAAM,WACjD,MAAA,QACA,QAAA,KACC,EAAS,aAAU,EACnB,EAAU,SAAU,GAE1B,EAoDO,cAIT,oDCjHA,UAA0C,YACxC,GAAc,IAEd,MAAM,EAAS,EAAA,YAET,GAAA,EAAe,EAAA,QAAO,IAAI,KAC1B,GAAA,EAAe,EAAA,SAAO,GACtB,GAAA,EAAc,EAAA,QAAO,IA4E3B,OA1EA,EAAA,EAAA,WAAA,KACE,GAAsB,oBAAX,OAAwB,OAK/B,OAAO,QAAQ,oBACjB,OAAO,QAAQ,kBAAoB,UAGrC,MAAM,EAAY,EAAa,QACzB,EAAa,GAChB,GAAoC,OAAS,EAEhD,EAAa,QAAU,EAAU,EAAO,QAAQ,SAAS,OACzD,EAAY,SAAA,EAAU,EAAA,YAAW,EAAO,QAAQ,UAEhD,IAAI,GAAY,EACZ,GAAS,EACT,GAAS,EAEb,MAAM,EAAW,EAAO,QAAQ,OAAA,EAAS,aAQvC,IAAsB,QAAX,EACP,IACJ,GAAY,EACZ,eAAA,KAEE,GADA,GAAY,GACP,EAAQ,OACb,MAAM,EAAS,EACf,GAAS,EACT,MAAM,SAAC,GAAY,EAAO,QACpB,EAAQ,EAAU,EAAS,OAC3B,GAAA,EAAO,EAAA,YAAW,GAClB,EAAY,EAAa,QAO/B,GALI,IAAU,GAEZ,EAAU,IAAI,EAAW,CAAC,EAAG,OAAO,QAAS,EAAG,OAAO,UAGrD,EAAQ,CACV,MAAM,EAAQ,EAAU,IAAI,IAAU,CAAC,EAAG,EAAG,EAAG,GAChD,OAAO,SAAS,EAAM,EAAG,EAAM,GAC/B,EAAU,IAAI,EAAO,EACvB,MACE,GACC,IAAU,GAAa,IAAS,EAAY,UAI7C,OAAO,SAAS,EAAG,GACnB,EAAU,IAAI,EAAO,CAAC,EAAG,EAAG,EAAG,KAKjC,EAAa,QAAU,EACvB,EAAY,QAAU,OAI1B,MAAA,KACE,GAAS,EACT,MAED,CAAC,EAAQ,IAEL,IACT,mGE7GA,SAKE,GAEA,OAAO,CACT,uNHTA,WACE,OAAA,EAAO,EAAA,YAAW,EACpB,kDI+KA,SAA0B,GACxB,MAAM,EAAS,EAAA,YAIT,GAAA,EAAY,EAAA,aACf,GAA8B,EAAO,QAAQ,OAAA,IAAa,KAC3D,CAAC,IAIG,GAAA,EAAc,EAAA,aAAA,IACZ,EAAO,QAAQ,SAAS,OAC9B,CAAC,IAGG,GAAA,EAAoB,EAAA,aAAA,IAAkB,GAAI,IAE1C,GAAA,EAAS,EAAA,sBACb,EACA,EACA,GAGF,OAAO,GAAA,EAAS,EAAA,iBAAgB,EAAQ,IAAM,EAAI,EAAA,kBAAiB,EACrE,0BAtLA,WACE,MAAM,EAAS,EAAA,YAIT,GAAA,EAAY,EAAA,aACf,GAA8B,EAAO,QAAQ,OAAA,IAAa,KAC3D,CAAC,IAIG,GAAA,EAAc,EAAA,aAAA,IACZ,EAAO,QAAQ,SAAS,OAC9B,CAAC,IAGG,GAAA,EAAoB,EAAA,aAAA,IAAkB,GAAI,IAoBhD,MAAO,CAAC,IAjBiB,iBAAA,EACvB,EAAA,sBAAqB,EAAW,EAAa,KAgBvC,EAbgB,EAAA,aAAA,CACrB,EAAM,KACL,MAAM,QAAC,GAAW,EACZ,EACY,mBAAT,EAEH,EAAK,IAAI,gBAAgB,EAAQ,SAAS,SAC1C,EACN,OAAO,EAAU,EAAQ,EAAO,WAAY,IAE9C,CAAC,IAIL,uBA0BA,SACE,GAKA,MAAM,EAAS,EAAA,YAEf,OAAA,EAAO,EAAA,aAAA,CACJ,EAAM,KACL,MAAM,QAAC,GAAW,EACZ,EACY,mBAAT,EACH,GAAA,EAAK,EAAA,kBAAiB,EAAQ,SAAS,SACvC,EAIA,GAAA,EAAY,EAAA,iBAAgB,EAAQ,EAAgB,IAI1D,OAAO,EACL,EACA,EAAgB,GAChB,IAGJ,CAAC,EAAQ,GAEb"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import{a as t,c as n,d as r,f as e,h as o,i,l as
|
|
2
|
-
return
|
|
3
|
-
return
|
|
4
|
-
return
|
|
1
|
+
import{a as t,c as n,d as r,f as e,h as o,i,l as c,m as s,o as a,p as u,r as l,s as f,t as h,u as d}from"./ssr-873l9jA-.js";import{createContext as y,useCallback as p,useContext as m,useEffect as v,useMemo as w,useRef as g,useState as x}from"react";import{createPath as C}from"history";import{jsx as k}from"react/jsx-runtime";import{commit as A,commitReplace as R,createHref as $,navigate as P,parseSearchInput as b,parseSearchSync as j,preload as I,resolveEntry as O,toLocation as S}from"@native-router/core";import{useSyncExternalStore as U}from"use-sync-external-store/shim";function _(t,n,r){return!(t.defaultPrevented||0!==t.button||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||"_blank"===n||"_parent"===n||"_top"===n||(r??"").split(/\s+/).includes("external"))}function K({to:t,...r}){const e=n(),o=g(!1);/* @__PURE__ */
|
|
2
|
+
return k("a",{...r,href:$(e,t),onClick:function(n){_(n,r.target,r.rel)&&(n.preventDefault(),o.current||(o.current=!0,P(e,t).catch(()=>{}).finally(()=>{o.current=!1})))}})}function L({to:t,end:r=!1,caseSensitive:e=!1,className:o,style:i,ariaCurrent:c,children:s,...a}){const u=n(),l=p(t=>u.history.listen(()=>t()),[u]),f=p(()=>u.history.location.pathname,[u]),h=U(l,f,f),d=S(u,t).pathname,[y,m]=e?[h,d]:[h.toLowerCase(),d.toLowerCase()],v=y===m,w=r||v?v:y.startsWith(m.endsWith("/")?m:`${m}/`),g={isActive:w,isExactActive:v};/* @__PURE__ */
|
|
3
|
+
return k(K,{to:t,...a,className:"function"==typeof o?o(g):o,style:"function"==typeof i?i(g):i,"aria-current":w?c??"page":void 0,children:"function"==typeof s?s(g):s})}var M=y({loading:!1});function D(){return m(M)}function E({to:t,prefetch:r="intent",children:e,...o}){const i=n(),c=g(null),s=g(void 0),a=g(!1),[u,l]=x(!1),[f,h]=x(),[d,y]=x();function p(){l(!0),h(void 0),a.current=!1;const n=I(i,t);return s.current=n,n.then(t=>t.task).then(t=>y(t),t=>{h(t),a.current=!0}).finally(()=>l(!1)),n}function m(){s.current||p()}v(()=>{s.current=void 0,a.current=!1,l(!1),h(void 0),y(void 0)},[t,i]),v(()=>{if("render"===r)return void m();if("viewport"!==r)return;const t=c.current;if(!t||"undefined"==typeof IntersectionObserver)return;const n=new IntersectionObserver(t=>{t.some(t=>t.isIntersecting)&&(n.disconnect(),m())});return n.observe(t),()=>n.disconnect()},[r,t,i]);const C=w(()=>({loading:u,error:f,view:d}),[u,f,d]),R="intent"===r?{onMouseEnter:m,onFocus:m}:void 0;/* @__PURE__ */
|
|
4
|
+
return k(M.Provider,{value:C,children:/* @__PURE__ */k("a",{...o,...R,ref:c,href:$(i,t),onClick:function(t){if(!_(t,o.target,o.rel))return;t.preventDefault();const n=s.current;(!n||a.current?p():n).then(t=>A(i,t.task,t.location)).catch(()=>{}).finally(()=>{s.current=void 0,a.current=!1})},children:e})})}function z({resetOnPush:t=!0}){const r=n(),e=g(/* @__PURE__ */new Map),o=g(-1),i=g("");return v(()=>{if("undefined"==typeof window)return;window.history.scrollRestoration&&(window.history.scrollRestoration="manual");const n=e.current,c=t=>t?.index||0;o.current=c(r.history.location.state),i.current=C(r.history.location);let s=!1,a=!1,u=!0;const l=r.history.listen(({action:e})=>{a||="POP"===e,s||(s=!0,queueMicrotask(()=>{if(s=!1,!u)return;const e=a;a=!1;const{location:l}=r.history,f=c(l.state),h=C(l),d=o.current;if(f!==d&&n.set(d,{x:window.scrollX,y:window.scrollY}),e){const t=n.get(f)??{x:0,y:0};window.scrollTo(t.x,t.y),n.set(f,t)}else!t||f===d&&h===i.current||(window.scrollTo(0,0),n.set(f,{x:0,y:0}));o.current=f,i.current=h}))});return()=>{u=!1,l()}},[r,t]),null}function N(t,n){return t.replace(/\\.|[:*]([A-Za-z_$][A-Za-z0-9_$]*)/g,(r,e)=>{if(void 0===e)return r;const o=n[e];if(void 0===o||0===o.length)throw new Error(`Missing param "${e}" for the path pattern "${t}"`);return(Array.isArray(o)?o:[o]).map(encodeURIComponent).join("/")})}var T=function({to:t,params:r,onClick:e,...o}){const i=n(),c=g(!1);let s=t;try{s=N(t,r??{})}catch{}/* @__PURE__ */
|
|
5
|
+
return k("a",{...o,href:$(i,s),onClick:function(n){if(e?.(n),!_(n,o.target,o.rel))return;if(n.preventDefault(),c.current)return;const s=N(t,r??{});c.current=!0,P(i,s).catch(()=>{}).finally(()=>{c.current=!1})}})};function W(t){return t}function Z(){const t=n(),r=p(n=>t.history.listen(()=>n()),[t]),e=p(()=>t.history.location.search,[t]),o=p(()=>"",[]);return[new URLSearchParams(U(r,e,o)),p((n,r)=>{const{history:e}=t,o="function"==typeof n?n(new URLSearchParams(e.location.search)):n;return V(t,o.toString(),r)},[t])]}function q(t){const r=n();return p((n,e)=>{const{history:o}=r,i="function"==typeof n?n(b(o.location.search)):n,c=j(t,F(i));return V(r,F(c),e)},[r,t])}function F(t){return Object.entries(t).filter(([,t])=>null!=t).map(([t,n])=>(Array.isArray(n)?n:[n]).map(n=>`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`).join("&")).join("&")}function V(t,n,r){const{history:e}=t,{pathname:o,hash:i}=e.location,c=o+(n?`?${n}`:"")+i;return r?.replace?O(t,S(t,c)).then(n=>R(t,n.task,n.location)):P(t,c)}function X(t){const r=n(),e=p(t=>r.history.listen(()=>t()),[r]),o=p(()=>r.history.location.search,[r]),i=p(()=>"",[]),c=U(e,o,i);return t?j(t,c):b(c)}export{l as HashRouter,i as HistoryRouter,K as Link,t as MemoryRouter,L as NavLink,E as PrefetchLink,a as Router,z as ScrollRestoration,T as TypedLink,d as View,f as createRouter,W as createRoutes,c as defaultResolveView,h as hydrate,r as useData,e as useLoading,u as useMatched,s as useNamedData,D as usePrefetch,n as useRouter,X as useSearch,Z as useSearchParams,q as useSetSearch,o as useView};
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/components/link-behavior.ts","../src/components/Link.tsx","../src/components/NavLink.tsx","../src/components/PrefetchLink.tsx","../src/components/ScrollRestoration.tsx","../src/use-search-params.ts"],"sourcesContent":["/**\n * Checks whether a click on an anchor should be intercepted and handled as an\n * in-app navigation, following the standard link interception semantics of\n * react-router/wouter: plain left clicks only.\n *\n * The browser keeps the default behavior whenever this returns `false`, so\n * modified clicks, middle clicks and links to other browsing contexts open in\n * a new tab/window as the user expects.\n *\n * @param e The click event to inspect.\n * @param target The anchor's `target` attribute, if any.\n * @param rel The anchor's `rel` attribute, if any.\n * @group Components\n */\n\nexport function shouldNavigate(\n e: {\n button: number;\n defaultPrevented: boolean;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n },\n target?: string,\n rel?: string\n): boolean {\n return (\n !e.defaultPrevented &&\n e.button === 0 &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey &&\n target !== '_blank' &&\n target !== '_parent' &&\n target !== '_top' &&\n !(rel ?? '').split(/\\s+/).includes('external')\n );\n}\n","import {createHref, navigate} from '@native-router/core';\nimport type {LinkProps} from '@@/types';\nimport {useRef, type MouseEvent} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\n/**\n * Link for navigate in app.\n * @param props\n * @group Components\n */\nexport default function Link({to, ...rest}: LinkProps) {\n const router = useRouter();\n const lockRef = useRef(false);\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n\n if (lockRef.current) return;\n lockRef.current = true;\n navigate(router, to)\n .catch(() => undefined)\n .finally(() => {\n lockRef.current = false;\n });\n }\n return (\n // eslint-disable-next-line jsx-a11y/anchor-has-content\n <a {...rest} href={createHref(router, to)} onClick={handleClick} />\n );\n}\n","import {toLocation} from '@native-router/core';\nimport type {NavLinkProps, NavLinkState} from '@@/types';\nimport {useCallback} from 'react';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './Router';\nimport Link from './Link';\n\n/**\n * Link that knows whether its target matches the current location.\n *\n * Active rules(aligned with react-router's `NavLink`):\n *\n * - `target` is the pathname of `toLocation(router, to)`(baseUrl prepended),\n * `current` is `router.history.location.pathname`; both are lowercased\n * unless `caseSensitive` is set.\n * - `isExactActive`: `current === target`.\n * - `isActive`: `isExactActive` when `end` is set, otherwise `current` equals\n * `target` or starts with `target` plus a trailing `/`(so `to=\"/\"` is active\n * for every path).\n *\n * While active the anchor renders `aria-current={ariaCurrent ?? 'page'}` and\n * `className`/`style`/`children` receive the active state when given as\n * functions. Click behavior is delegated to {@link Link}, inheriting the\n * modified-click guard and the double-click lock.\n * @param props\n * @group Components\n */\nexport default function NavLink({\n to,\n end = false,\n caseSensitive = false,\n className,\n style,\n ariaCurrent,\n children,\n ...rest\n}: NavLinkProps) {\n const router = useRouter();\n // Subscribe to the history location so the active state stays in sync even\n // when rendered outside the routed view(e.g. a nav bar beside <View />).\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n const getSnapshot = useCallback(\n () => router.history.location.pathname,\n [router]\n );\n const current = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const target = toLocation(router, to).pathname;\n const [currentPath, targetPath] = caseSensitive\n ? [current, target]\n : [current.toLowerCase(), target.toLowerCase()];\n\n const isExactActive = currentPath === targetPath;\n // A root `to=\"/\"` normalizes to the \"/\" prefix and matches every path.\n const isActive =\n end || isExactActive\n ? isExactActive\n : currentPath.startsWith(\n targetPath.endsWith('/') ? targetPath : `${targetPath}/`\n );\n\n const state: NavLinkState = {isActive, isExactActive};\n\n return (\n <Link\n to={to}\n {...rest}\n className={typeof className === 'function' ? className(state) : className}\n style={typeof style === 'function' ? style(state) : style}\n aria-current={isActive ? (ariaCurrent ?? 'page') : undefined}\n >\n {typeof children === 'function' ? children(state) : children}\n </Link>\n );\n}\n","import {commit, createHref, preload} from '@native-router/core';\nimport type {ResolvedEntry} from '@native-router/core';\nimport type {LinkProps, Route} from '@@/types';\nimport {\n createContext,\n MouseEvent,\n ReactNode,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\ntype PrefetchLinkContext = {loading: boolean; error?: Error; view?: ReactNode};\n\nconst Context = createContext<PrefetchLinkContext>({loading: false});\n\n/**\n * Get the prefetch context. Use for render a preview view.\n * @group Hooks\n */\nexport function usePrefetch() {\n return useContext(Context);\n}\n\n/**\n * Link with prefetch support.\n *\n * The `prefetch` prop controls when the target view is resolved:\n * - `'intent'` (default): prefetch on hover or focus.\n * - `'render'`: prefetch as soon as the link mounts.\n * - `'viewport'`: prefetch when the link scrolls into the viewport.\n * - `'none'`: never prefetch; the target is resolved on click.\n *\n * @param props\n * @group Components\n */\nexport default function PrefetchLink({\n to,\n prefetch = 'intent',\n children,\n ...rest\n}: LinkProps) {\n const router = useRouter();\n const anchorRef = useRef<HTMLAnchorElement>(null);\n const entryRef = useRef<Promise<ResolvedEntry<ReactNode>> | undefined>(\n undefined\n );\n const failedRef = useRef(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error>();\n const [view, setView] = useState<ReactNode>();\n\n function prefetchIt(): Promise<ResolvedEntry<ReactNode>> {\n setLoading(true);\n setError(undefined);\n failedRef.current = false;\n // Route guards(redirect/beforeLoad) run before the view resolves; the\n // stored entry carries the terminal location, so prefetch, preview and\n // commit all agree on the final target. preload() caches the entry at\n // the router level(keyed by pathname+search, TTL bounded), so repeated\n // prefetches of the same target share one resolution and the entry is\n // evicted once committed.\n const entryPromise = preload<Route, ReactNode>(router, to);\n entryRef.current = entryPromise;\n // The derived chain carries the loading/error/view state and handles\n // the rejection of the entry task, so a prefetched task that is never\n // committed does not surface as a global unhandledrejection. Failure is\n // tracked in `failedRef` instead of relying on the rejected task itself.\n entryPromise\n .then((entry) => entry.task)\n .then(\n (v) => setView(v),\n (e) => {\n setError(e);\n failedRef.current = true;\n }\n )\n .finally(() => setLoading(false));\n return entryPromise;\n }\n\n function handlePrefetch() {\n if (entryRef.current) return;\n prefetchIt();\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n // A stored entry that already failed can never be committed\n // successfully, so resolve the target again before committing.\n const stored = entryRef.current;\n const entryPromise = !stored || failedRef.current ? prefetchIt() : stored;\n // The terminal location of the entry is committed, not the link target.\n entryPromise\n .then((entry) => commit(router, entry.task, entry.location))\n .catch(() => undefined)\n .finally(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n });\n }\n\n // Reset every piece of prefetch state when the target changes, so a stale\n // error or preview from the previous target is not rendered for the new one.\n useEffect(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n setLoading(false);\n setError(undefined);\n setView(undefined);\n }, [to, router]);\n\n useEffect(() => {\n if (prefetch === 'render') {\n handlePrefetch();\n return undefined;\n }\n if (prefetch !== 'viewport') return undefined;\n const el = anchorRef.current;\n if (!el || typeof IntersectionObserver === 'undefined') return undefined;\n // eslint-disable-next-line compat/compat -- guarded above at runtime\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return;\n observer.disconnect();\n handlePrefetch();\n });\n observer.observe(el);\n return () => observer.disconnect();\n // `location` and `handlePrefetch` are derived from these deps.\n }, [prefetch, to, router]);\n\n const linkContext = useMemo(\n () => ({loading, error, view}),\n [loading, error, view]\n );\n\n // Hover/focus are intent signals only; 'render', 'viewport' and 'none'\n // never prefetch on interaction.\n const intentHandlers =\n prefetch === 'intent'\n ? {onMouseEnter: handlePrefetch, onFocus: handlePrefetch}\n : undefined;\n\n return (\n <Context.Provider value={linkContext}>\n <a\n {...rest}\n {...intentHandlers}\n ref={anchorRef}\n href={createHref(router, to)}\n onClick={handleClick}\n >\n {children}\n </a>\n </Context.Provider>\n );\n}\n","import {createPath} from 'history';\nimport type {HistoryState} from '@native-router/core';\nimport {useEffect, useRef} from 'react';\nimport {useRouter} from './Router';\n\n/** Saved scroll offset of one history stack slot. */\ntype ScrollPosition = {x: number; y: number};\n\ntype ScrollRestorationProps = {\n /**\n * Scroll a freshly pushed(or replaced) entry back to the top, like a full\n * page load. Set to false to keep the current offset across forward\n * navigations(e.g. feed-style \"load more\" pages). POP always restores the\n * saved offset, regardless of this flag.\n * @default true\n */\n resetOnPush?: boolean;\n};\n\n/**\n * Restore the window scroll position across history navigations.\n *\n * The router restores views from the in-memory `viewStack`: on back/forward\n * the view is reused without re-fetching its data, but the browser has\n * already left the old document position and a remounted DOM starts at the\n * top by default. Mount `<ScrollRestoration />` anywhere inside\n * {@link Router}(typically in the root layout) to fill that gap:\n *\n * - the scroll offset of every visited entry is remembered, keyed by the\n * absolute history index(`history.location.state.index` — the same key\n * `viewStack` is keyed by). Like `viewStack`, the map is in-memory and\n * session scoped: after a page reload there is nothing to restore and the\n * browser's own restoration takes over.\n * - `history.scrollRestoration` is taken over as `manual`(the browser's\n * own `auto` restoration would race the restore and pre-scroll while the\n * left entry's offset is still being read), making the component solely\n * responsible for restoration; the takeover is session scoped and not\n * reverted on unmount, like `viewStack`.\n * - POP restores the saved offset of the landed entry(`0,0` when none was\n * saved, e.g. forward-past-the-end or post-reload entries).\n * - PUSH and REPLACE scroll a fresh entry back to the top when\n * `resetOnPush` is set(default). Internal re-commits of the very same\n * entry(the core listener's POP sync, `refresh`, listen bootstrap) never\n * touch the scroll.\n *\n * Renders nothing. SSR safe: it only subscribes in an effect and only when\n * `window` exists.\n * @param props\n * @group Components\n */\nexport default function ScrollRestoration({\n resetOnPush = true\n}: ScrollRestorationProps) {\n const router = useRouter();\n // Positions survive effect re-runs(resetOnPush changes) on purpose.\n const positionsRef = useRef(new Map<number, ScrollPosition>());\n const lastIndexRef = useRef(-1);\n const lastPathRef = useRef('');\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n // The browser's own `auto` restoration races this component's restore\n // (it can pre-scroll within the microtask window where the left entry's\n // offset is still being read), so take full control for the session.\n if (window.history.scrollRestoration) {\n window.history.scrollRestoration = 'manual';\n }\n\n const positions = positionsRef.current;\n const readIndex = (state: unknown) =>\n (state as HistoryState | undefined)?.index || 0;\n\n lastIndexRef.current = readIndex(router.history.location.state);\n lastPathRef.current = createPath(router.history.location);\n\n let scheduled = false;\n let sawPop = false;\n let active = true;\n\n const unlisten = router.history.listen(({action}) => {\n // The core listener re-commits a landed POP entry with an internal\n // REPLACE(stack serialization sync), and listener registration order\n // decides whether that REPLACE reaches us before or after the POP\n // itself. Collapse the whole synchronous event storm and decide once,\n // from the final history state, in a microtask — no layout has\n // scrolled yet at that point, so saving the left entry's offset\n // still reads the pre-navigation scroll.\n sawPop ||= action === 'POP';\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n if (!active) return;\n const popped = sawPop;\n sawPop = false;\n const {location} = router.history;\n const index = readIndex(location.state);\n const path = createPath(location);\n const lastIndex = lastIndexRef.current;\n\n if (index !== lastIndex) {\n // The entry we just left keeps its scroll offset.\n positions.set(lastIndex, {x: window.scrollX, y: window.scrollY});\n }\n\n if (popped) {\n const saved = positions.get(index) ?? {x: 0, y: 0};\n window.scrollTo(saved.x, saved.y);\n positions.set(index, saved);\n } else if (\n resetOnPush &&\n (index !== lastIndex || path !== lastPathRef.current)\n ) {\n // A fresh forward entry, or the current entry rewritten to a\n // different location(a replace navigation) — start at the top.\n window.scrollTo(0, 0);\n positions.set(index, {x: 0, y: 0});\n }\n // Anything else is an internal re-commit of the same entry: keep\n // the current scroll.\n\n lastIndexRef.current = index;\n lastPathRef.current = path;\n });\n });\n\n return () => {\n active = false;\n unlisten();\n };\n }, [router, resetOnPush]);\n\n return null;\n}\n","import {useCallback} from 'react';\nimport {\n commitReplace,\n navigate,\n parseSearchInput,\n parseSearchSync,\n resolveEntry,\n toLocation\n} from '@native-router/core';\nimport type {\n SearchInput,\n SearchOutputOf,\n StandardSchemaV1\n} from '@native-router/core';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './components/Router';\n\ntype SetSearchParams = (\n next: URLSearchParams | ((prev: URLSearchParams) => URLSearchParams),\n opts?: {replace?: boolean}\n) => Promise<void> | void;\n\n/**\n * Read and write the search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as the Router Component), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; a fresh `URLSearchParams` view is derived from that string\n * on each render.\n *\n * The setter navigates like any other route change: by default the new\n * search is pushed onto the history stack(mainstream react-router\n * semantics), pass `{replace: true}` to rewrite the current entry instead.\n * Since the search is part of the location, every write re-resolves the\n * matched route, so route `data` fetchers(see {@link useData}) observe the\n * new search on the next {@link useData} read.\n *\n * @group Hooks\n * @returns [searchParams, setSearchParams] - the current search params and\n * the setter(functional updates receive the live previous params); the\n * setter returns a `Promise<void>` that resolves once the navigation\n * commits, so callers may optionally `await` it\n */\nexport function useSearchParams(): [URLSearchParams, SetSearchParams] {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a URLSearchParams instance) to keep the\n // snapshot reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n const searchParams = new URLSearchParams(\n useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n );\n\n const setSearchParams = useCallback<SetSearchParams>(\n (next, opts) => {\n const {history} = router;\n const params =\n typeof next === 'function'\n ? // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n next(new URLSearchParams(history.location.search))\n : next;\n const qs = params.toString();\n const {pathname, hash} = history.location;\n const to = pathname + (qs ? `?${qs}` : '') + hash;\n if (opts?.replace) {\n // Route guards run like any other navigation(align with the push\n // branch): the entry carries the terminal location, so a redirect\n // replaces the current entry with its final target.\n return resolveEntry(router, toLocation(router, to)).then((entry) =>\n commitReplace(router, entry.task, entry.location)\n );\n }\n return navigate(router, to);\n },\n [router]\n );\n\n return [searchParams, setSearchParams];\n}\n\n/**\n * Read the parsed search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as {@link useSearchParams}), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; the parse itself runs on each render.\n *\n * Without a schema the hook degrades to the raw input object of\n * `parseSearchInput` — strings, arrays for repeated keys\n * (`{page: '2', tag: ['a', 'b']}`). With a schema — any zod/valibot/\n * arktype schema, see the route {@link Route.search search field} — the\n * returned object is the schema's parsed output, so coercion and defaults\n * apply, e.g. `useSearch(pageSchema).page` is a number.\n *\n * Prefer declaring the schema once on the route: its `data` loader then\n * receives a parsed `ctx.search` during resolve, and an invalid search\n * fails the navigation through the existing error channels instead of\n * throwing during render.\n *\n * @group Hooks\n * @param schema an optional Standard Schema validator of the search; it\n * must validate synchronously\n * @returns the parsed search params of the current location\n * @throws {SearchError} when `schema` rejects the current search\n */\nexport function useSearch<S extends StandardSchemaV1>(\n schema: S\n): SearchOutputOf<S>;\nexport function useSearch(): SearchInput;\nexport function useSearch(schema?: StandardSchemaV1): unknown {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a parsed object) to keep the snapshot\n // reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n const search = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getServerSnapshot\n );\n\n return schema ? parseSearchSync(schema, search) : parseSearchInput(search);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/components/link-behavior.ts","../src/components/Link.tsx","../src/components/NavLink.tsx","../src/components/PrefetchLink.tsx","../src/components/ScrollRestoration.tsx","../src/components/TypedLink.tsx","../src/create-routes.js","../src/use-search-params.ts"],"sourcesContent":["/**\n * Checks whether a click on an anchor should be intercepted and handled as an\n * in-app navigation, following the standard link interception semantics of\n * react-router/wouter: plain left clicks only.\n *\n * The browser keeps the default behavior whenever this returns `false`, so\n * modified clicks, middle clicks and links to other browsing contexts open in\n * a new tab/window as the user expects.\n *\n * @param e The click event to inspect.\n * @param target The anchor's `target` attribute, if any.\n * @param rel The anchor's `rel` attribute, if any.\n * @group Components\n */\n\nexport function shouldNavigate(\n e: {\n button: number;\n defaultPrevented: boolean;\n metaKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n },\n target?: string,\n rel?: string\n): boolean {\n return (\n !e.defaultPrevented &&\n e.button === 0 &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey &&\n target !== '_blank' &&\n target !== '_parent' &&\n target !== '_top' &&\n !(rel ?? '').split(/\\s+/).includes('external')\n );\n}\n","import {createHref, navigate} from '@native-router/core';\nimport type {LinkProps} from '@@/types';\nimport {useRef, type MouseEvent} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\n/**\n * Link for navigate in app.\n * @param props\n * @group Components\n */\nexport default function Link({to, ...rest}: LinkProps) {\n const router = useRouter();\n const lockRef = useRef(false);\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n\n if (lockRef.current) return;\n lockRef.current = true;\n navigate(router, to)\n .catch(() => undefined)\n .finally(() => {\n lockRef.current = false;\n });\n }\n return (\n // eslint-disable-next-line jsx-a11y/anchor-has-content\n <a {...rest} href={createHref(router, to)} onClick={handleClick} />\n );\n}\n","import {toLocation} from '@native-router/core';\nimport type {NavLinkProps, NavLinkState} from '@@/types';\nimport {useCallback} from 'react';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './Router';\nimport Link from './Link';\n\n/**\n * Link that knows whether its target matches the current location.\n *\n * Active rules(aligned with react-router's `NavLink`):\n *\n * - `target` is the pathname of `toLocation(router, to)`(baseUrl prepended),\n * `current` is `router.history.location.pathname`; both are lowercased\n * unless `caseSensitive` is set.\n * - `isExactActive`: `current === target`.\n * - `isActive`: `isExactActive` when `end` is set, otherwise `current` equals\n * `target` or starts with `target` plus a trailing `/`(so `to=\"/\"` is active\n * for every path).\n *\n * While active the anchor renders `aria-current={ariaCurrent ?? 'page'}` and\n * `className`/`style`/`children` receive the active state when given as\n * functions. Click behavior is delegated to {@link Link}, inheriting the\n * modified-click guard and the double-click lock.\n * @param props\n * @group Components\n */\nexport default function NavLink({\n to,\n end = false,\n caseSensitive = false,\n className,\n style,\n ariaCurrent,\n children,\n ...rest\n}: NavLinkProps) {\n const router = useRouter();\n // Subscribe to the history location so the active state stays in sync even\n // when rendered outside the routed view(e.g. a nav bar beside <View />).\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n const getSnapshot = useCallback(\n () => router.history.location.pathname,\n [router]\n );\n const current = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const target = toLocation(router, to).pathname;\n const [currentPath, targetPath] = caseSensitive\n ? [current, target]\n : [current.toLowerCase(), target.toLowerCase()];\n\n const isExactActive = currentPath === targetPath;\n // A root `to=\"/\"` normalizes to the \"/\" prefix and matches every path.\n const isActive =\n end || isExactActive\n ? isExactActive\n : currentPath.startsWith(\n targetPath.endsWith('/') ? targetPath : `${targetPath}/`\n );\n\n const state: NavLinkState = {isActive, isExactActive};\n\n return (\n <Link\n to={to}\n {...rest}\n className={typeof className === 'function' ? className(state) : className}\n style={typeof style === 'function' ? style(state) : style}\n aria-current={isActive ? (ariaCurrent ?? 'page') : undefined}\n >\n {typeof children === 'function' ? children(state) : children}\n </Link>\n );\n}\n","import {commit, createHref, preload} from '@native-router/core';\nimport type {ResolvedEntry} from '@native-router/core';\nimport type {LinkProps, Route} from '@@/types';\nimport {\n createContext,\n MouseEvent,\n ReactNode,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\ntype PrefetchLinkContext = {loading: boolean; error?: Error; view?: ReactNode};\n\nconst Context = createContext<PrefetchLinkContext>({loading: false});\n\n/**\n * Get the prefetch context. Use for render a preview view.\n * @group Hooks\n */\nexport function usePrefetch() {\n return useContext(Context);\n}\n\n/**\n * Link with prefetch support.\n *\n * The `prefetch` prop controls when the target view is resolved:\n * - `'intent'` (default): prefetch on hover or focus.\n * - `'render'`: prefetch as soon as the link mounts.\n * - `'viewport'`: prefetch when the link scrolls into the viewport.\n * - `'none'`: never prefetch; the target is resolved on click.\n *\n * @param props\n * @group Components\n */\nexport default function PrefetchLink({\n to,\n prefetch = 'intent',\n children,\n ...rest\n}: LinkProps) {\n const router = useRouter();\n const anchorRef = useRef<HTMLAnchorElement>(null);\n const entryRef = useRef<Promise<ResolvedEntry<ReactNode>> | undefined>(\n undefined\n );\n const failedRef = useRef(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error>();\n const [view, setView] = useState<ReactNode>();\n\n function prefetchIt(): Promise<ResolvedEntry<ReactNode>> {\n setLoading(true);\n setError(undefined);\n failedRef.current = false;\n // Route guards(redirect/beforeLoad) run before the view resolves; the\n // stored entry carries the terminal location, so prefetch, preview and\n // commit all agree on the final target. preload() caches the entry at\n // the router level(keyed by pathname+search, TTL bounded), so repeated\n // prefetches of the same target share one resolution and the entry is\n // evicted once committed.\n const entryPromise = preload<Route, ReactNode>(router, to);\n entryRef.current = entryPromise;\n // The derived chain carries the loading/error/view state and handles\n // the rejection of the entry task, so a prefetched task that is never\n // committed does not surface as a global unhandledrejection. Failure is\n // tracked in `failedRef` instead of relying on the rejected task itself.\n entryPromise\n .then((entry) => entry.task)\n .then(\n (v) => setView(v),\n (e) => {\n setError(e);\n failedRef.current = true;\n }\n )\n .finally(() => setLoading(false));\n return entryPromise;\n }\n\n function handlePrefetch() {\n if (entryRef.current) return;\n prefetchIt();\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n // A stored entry that already failed can never be committed\n // successfully, so resolve the target again before committing.\n const stored = entryRef.current;\n const entryPromise = !stored || failedRef.current ? prefetchIt() : stored;\n // The terminal location of the entry is committed, not the link target.\n entryPromise\n .then((entry) => commit(router, entry.task, entry.location))\n .catch(() => undefined)\n .finally(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n });\n }\n\n // Reset every piece of prefetch state when the target changes, so a stale\n // error or preview from the previous target is not rendered for the new one.\n useEffect(() => {\n entryRef.current = undefined;\n failedRef.current = false;\n setLoading(false);\n setError(undefined);\n setView(undefined);\n }, [to, router]);\n\n useEffect(() => {\n if (prefetch === 'render') {\n handlePrefetch();\n return undefined;\n }\n if (prefetch !== 'viewport') return undefined;\n const el = anchorRef.current;\n if (!el || typeof IntersectionObserver === 'undefined') return undefined;\n // eslint-disable-next-line compat/compat -- guarded above at runtime\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return;\n observer.disconnect();\n handlePrefetch();\n });\n observer.observe(el);\n return () => observer.disconnect();\n // `location` and `handlePrefetch` are derived from these deps.\n }, [prefetch, to, router]);\n\n const linkContext = useMemo(\n () => ({loading, error, view}),\n [loading, error, view]\n );\n\n // Hover/focus are intent signals only; 'render', 'viewport' and 'none'\n // never prefetch on interaction.\n const intentHandlers =\n prefetch === 'intent'\n ? {onMouseEnter: handlePrefetch, onFocus: handlePrefetch}\n : undefined;\n\n return (\n <Context.Provider value={linkContext}>\n <a\n {...rest}\n {...intentHandlers}\n ref={anchorRef}\n href={createHref(router, to)}\n onClick={handleClick}\n >\n {children}\n </a>\n </Context.Provider>\n );\n}\n","import {createPath} from 'history';\nimport type {HistoryState} from '@native-router/core';\nimport {useEffect, useRef} from 'react';\nimport {useRouter} from './Router';\n\n/** Saved scroll offset of one history stack slot. */\ntype ScrollPosition = {x: number; y: number};\n\ntype ScrollRestorationProps = {\n /**\n * Scroll a freshly pushed(or replaced) entry back to the top, like a full\n * page load. Set to false to keep the current offset across forward\n * navigations(e.g. feed-style \"load more\" pages). POP always restores the\n * saved offset, regardless of this flag.\n * @default true\n */\n resetOnPush?: boolean;\n};\n\n/**\n * Restore the window scroll position across history navigations.\n *\n * The router restores views from the in-memory `viewStack`: on back/forward\n * the view is reused without re-fetching its data, but the browser has\n * already left the old document position and a remounted DOM starts at the\n * top by default. Mount `<ScrollRestoration />` anywhere inside\n * {@link Router}(typically in the root layout) to fill that gap:\n *\n * - the scroll offset of every visited entry is remembered, keyed by the\n * absolute history index(`history.location.state.index` — the same key\n * `viewStack` is keyed by). Like `viewStack`, the map is in-memory and\n * session scoped: after a page reload there is nothing to restore and the\n * browser's own restoration takes over.\n * - `history.scrollRestoration` is taken over as `manual`(the browser's\n * own `auto` restoration would race the restore and pre-scroll while the\n * left entry's offset is still being read), making the component solely\n * responsible for restoration; the takeover is session scoped and not\n * reverted on unmount, like `viewStack`.\n * - POP restores the saved offset of the landed entry(`0,0` when none was\n * saved, e.g. forward-past-the-end or post-reload entries).\n * - PUSH and REPLACE scroll a fresh entry back to the top when\n * `resetOnPush` is set(default). Internal re-commits of the very same\n * entry(the core listener's POP sync, `refresh`, listen bootstrap) never\n * touch the scroll.\n *\n * Renders nothing. SSR safe: it only subscribes in an effect and only when\n * `window` exists.\n * @param props\n * @group Components\n */\nexport default function ScrollRestoration({\n resetOnPush = true\n}: ScrollRestorationProps) {\n const router = useRouter();\n // Positions survive effect re-runs(resetOnPush changes) on purpose.\n const positionsRef = useRef(new Map<number, ScrollPosition>());\n const lastIndexRef = useRef(-1);\n const lastPathRef = useRef('');\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n // The browser's own `auto` restoration races this component's restore\n // (it can pre-scroll within the microtask window where the left entry's\n // offset is still being read), so take full control for the session.\n if (window.history.scrollRestoration) {\n window.history.scrollRestoration = 'manual';\n }\n\n const positions = positionsRef.current;\n const readIndex = (state: unknown) =>\n (state as HistoryState | undefined)?.index || 0;\n\n lastIndexRef.current = readIndex(router.history.location.state);\n lastPathRef.current = createPath(router.history.location);\n\n let scheduled = false;\n let sawPop = false;\n let active = true;\n\n const unlisten = router.history.listen(({action}) => {\n // The core listener re-commits a landed POP entry with an internal\n // REPLACE(stack serialization sync), and listener registration order\n // decides whether that REPLACE reaches us before or after the POP\n // itself. Collapse the whole synchronous event storm and decide once,\n // from the final history state, in a microtask — no layout has\n // scrolled yet at that point, so saving the left entry's offset\n // still reads the pre-navigation scroll.\n sawPop ||= action === 'POP';\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n if (!active) return;\n const popped = sawPop;\n sawPop = false;\n const {location} = router.history;\n const index = readIndex(location.state);\n const path = createPath(location);\n const lastIndex = lastIndexRef.current;\n\n if (index !== lastIndex) {\n // The entry we just left keeps its scroll offset.\n positions.set(lastIndex, {x: window.scrollX, y: window.scrollY});\n }\n\n if (popped) {\n const saved = positions.get(index) ?? {x: 0, y: 0};\n window.scrollTo(saved.x, saved.y);\n positions.set(index, saved);\n } else if (\n resetOnPush &&\n (index !== lastIndex || path !== lastPathRef.current)\n ) {\n // A fresh forward entry, or the current entry rewritten to a\n // different location(a replace navigation) — start at the top.\n window.scrollTo(0, 0);\n positions.set(index, {x: 0, y: 0});\n }\n // Anything else is an internal re-commit of the same entry: keep\n // the current scroll.\n\n lastIndexRef.current = index;\n lastPathRef.current = path;\n });\n });\n\n return () => {\n active = false;\n unlisten();\n };\n }, [router, resetOnPush]);\n\n return null;\n}\n","import {createHref, navigate} from '@native-router/core';\nimport {useRef, type MouseEvent, type ReactElement} from 'react';\nimport type {TypedLinkProps} from '@@/types';\nimport {useRouter} from './Router';\nimport {shouldNavigate} from './link-behavior';\n\n/**\n * Interpolate params into a path pattern: `:name` segments take a\n * string, `*name` wildcards a string array(joined with `/`), both\n * percent-encoded; everything else — including `\\` escapes — is static\n * text. The grammar matches the core matcher's(the ASCII identifier\n * scanner the type-level `ExtractPathParams` models).\n * @throws when a required param is missing or empty — the click-time\n * params check of {@link TypedLink}\n */\nfunction interpolatePath(\n pattern: string,\n params: Record<string, string | string[]>\n): string {\n return pattern.replace(\n /\\\\.|[:*]([A-Za-z_$][A-Za-z0-9_$]*)/g,\n (match, name?: string) => {\n if (name === undefined) return match;\n const value = params[name];\n if (value === undefined || value.length === 0) {\n throw new Error(\n `Missing param \"${name}\" for the path pattern \"${pattern}\"`\n );\n }\n return (Array.isArray(value) ? value : [value])\n .map(encodeURIComponent)\n .join('/');\n }\n );\n}\n\n/**\n * Link whose `to` is narrowed to a route table's path patterns and whose\n * `params` is checked against the exact pattern's param segments. Give\n * it the pattern union as its type argument:\n *\n * ```tsx\n * const routes = createRoutes({children: [{path: '/users/:id'}, ...]});\n *\n * <TypedLink<RoutePaths<typeof routes>> to=\"/users/:id\" params={{id: '7'}}>\n * User 7\n * </TypedLink>\n * ```\n *\n * A `to` outside the table, a missing required param or a wrong param\n * shape is a compile error; at click time the params are interpolated\n * into the pattern and a missing required param throws instead of\n * navigating(the type-level check's runtime backstop).\n *\n * Without the type argument the component degrades to a plain `Link`:\n * any path, params optional. Click interception follows {@link Link}\n * — only plain primary-button clicks are intercepted.\n * @group Components\n * @param props `to`(a pattern of the table), `params`(per the pattern)\n * and the usual anchor attributes\n */\n// The implementation works on the loose shape; the typed signature is\n// attached below so discriminated-union props need not be destructured\n// across the union.\nfunction TypedLinkImpl({\n to,\n params,\n onClick,\n ...rest\n}: {\n to: string;\n params?: Record<string, string | string[]>;\n} & Omit<TypedLinkProps, 'to' | 'params' | 'prefetch' | 'href'>) {\n const router = useRouter();\n const lockRef = useRef(false);\n\n // The href shows the interpolated target; when a required param is\n // missing the raw pattern stays and the click-time check below blocks\n // the navigation.\n let href: string = to;\n try {\n href = interpolatePath(to, params ?? {});\n } catch {\n // Programming error the types already flag; surfaced on click.\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n onClick?.(e);\n // Modified clicks, other buttons and links to another browsing context\n // keep the browser default behavior (open in new tab/window, etc).\n if (!shouldNavigate(e, rest.target, rest.rel)) return;\n e.preventDefault();\n\n if (lockRef.current) return;\n // Click-time params check per the pattern's param segments: a missing\n // required param throws instead of navigating.\n const target = interpolatePath(to, params ?? {});\n lockRef.current = true;\n navigate(router, target)\n .catch(() => undefined)\n .finally(() => {\n lockRef.current = false;\n });\n }\n\n return (\n // eslint-disable-next-line jsx-a11y/anchor-has-content\n <a {...rest} href={createHref(router, href)} onClick={handleClick} />\n );\n}\n\nconst TypedLink = TypedLinkImpl as <Paths extends string = string>(\n props: TypedLinkProps<Paths>\n) => ReactElement | null;\n\nexport default TypedLink;\n","/**\n * Identity function with `satisfies` semantics: the table is checked\n * against `Route` while every `path` keeps its string-literal type, so\n * `RoutePaths<typeof routes>` can extract the full pattern union for\n * `TypedLink`. An `as Route` assertion does the opposite — it widens\n * every `path` to `string` and gives up the literals.\n *\n * ```tsx\n * const routes = createRoutes({\n * children: [\n * {path: '/', component: () => import('./Home')},\n * {path: '/users/:id', component: () => import('./UserProfile')}\n * ]\n * });\n * // type AppPaths = '/' | '/users/:id'\n * type AppPaths = RoutePaths<typeof routes>;\n * ```\n *\n * Zero runtime cost: the function returns its argument unchanged and\n * tree-shakes away.\n * @group Methods\n * @category Route\n * @param routes the route table, a route object or an array of them\n * @returns the very same route table, literal types preserved\n */\nexport function createRoutes(\n // The `const` modifier keeps every `path` a string literal(through\n // arbitrary nesting) while the parameter is still checked against\n // `Route` — the `satisfies` semantics an `as Route` assertion lacks:\n // the assertion widens every `path` to `string` instead.\n routes\n) {\n return routes;\n}\n","import {useCallback} from 'react';\nimport {\n commitReplace,\n navigate,\n parseSearchInput,\n parseSearchSync,\n resolveEntry,\n toLocation\n} from '@native-router/core';\nimport type {\n SearchInput,\n SearchOutputOf,\n StandardSchemaV1\n} from '@native-router/core';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport {useRouter} from './components/Router';\n\ntype SetSearchParams = (\n next: URLSearchParams | ((prev: URLSearchParams) => URLSearchParams),\n opts?: {replace?: boolean}\n) => Promise<void> | void;\n\n/**\n * Read and write the search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as the Router Component), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; a fresh `URLSearchParams` view is derived from that string\n * on each render.\n *\n * The setter navigates like any other route change: by default the new\n * search is pushed onto the history stack(mainstream react-router\n * semantics), pass `{replace: true}` to rewrite the current entry instead.\n * Since the search is part of the location, every write re-resolves the\n * matched route, so route `data` fetchers(see {@link useData}) observe the\n * new search on the next {@link useData} read.\n *\n * @group Hooks\n * @returns [searchParams, setSearchParams] - the current search params and\n * the setter(functional updates receive the live previous params); the\n * setter returns a `Promise<void>` that resolves once the navigation\n * commits, so callers may optionally `await` it\n */\nexport function useSearchParams(): [URLSearchParams, SetSearchParams] {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a URLSearchParams instance) to keep the\n // snapshot reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n const searchParams = new URLSearchParams(\n useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n );\n\n const setSearchParams = useCallback<SetSearchParams>(\n (next, opts) => {\n const {history} = router;\n const params =\n typeof next === 'function'\n ? // eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled\n next(new URLSearchParams(history.location.search))\n : next;\n return setSearch(router, params.toString(), opts);\n },\n [router]\n );\n\n return [searchParams, setSearchParams];\n}\n\n/**\n * Write the search params of the current location through a schema, the\n * setter-side twin of `useSearch(schema)`: the next value is serialized\n * to a query string, degraded with `parseSearchInput` and validated by\n * the SAME schema before any navigation happens. A schema that rejects\n * the value throws its issues(`SearchError`) without touching the\n * location; the value the schema would default or coerce on the read\n * side never gets silently written.\n *\n * Synchronous schemas only(the `useSearch` flavor); an async `validate`\n * rejects without navigating.\n *\n * The navigation semantics follow `useSearchParams`' setter: push by\n * default, `{replace: true}` rewrites the current entry, guards run,\n * and the returned `Promise<void>` resolves once the navigation commits.\n *\n * @group Hooks\n * @param schema a Standard Schema validator of the search — must\n * validate synchronously\n * @returns the schema-aware setter; functional updates receive the live\n * previous params\n * @throws {SearchError} when `schema` rejects the next value, before\n * any navigation\n */\nexport function useSetSearch<S extends StandardSchemaV1>(\n schema: S\n): (\n next: SearchInput | ((prev: SearchInput) => SearchInput),\n opts?: {replace?: boolean}\n) => Promise<void> | void {\n const router = useRouter();\n\n return useCallback(\n (next, opts) => {\n const {history} = router;\n const input =\n typeof next === 'function'\n ? next(parseSearchInput(history.location.search))\n : next;\n // Validate the whole next value — not a diff — the same way a\n // navigation to the resulting URL would: parseSearchSync throws\n // SearchError with the schema's issues, and no navigation happens.\n const validated = parseSearchSync(schema, stringifySearch(input));\n // The schema output is authoritative: defaults applied and values\n // coerced to strings, so a partially-filled input still writes the\n // fully-defaulted query.\n return setSearch(\n router,\n stringifySearch(validated as Record<string, unknown>),\n opts\n );\n },\n [router, schema]\n );\n}\n\nfunction stringifySearch(input: Record<string, unknown>): string {\n return Object.entries(input)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) =>\n (Array.isArray(value) ? value : [value])\n .map(\n (v) => `${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`\n )\n .join('&')\n )\n .join('&');\n}\n\nfunction setSearch(\n router: ReturnType<typeof useRouter>,\n qs: string,\n opts?: {replace?: boolean}\n): Promise<void> | void {\n const {history} = router;\n const {pathname, hash} = history.location;\n const to = pathname + (qs ? `?${qs}` : '') + hash;\n if (opts?.replace) {\n // Route guards run like any other navigation(align with the push\n // branch): the entry carries the terminal location, so a redirect\n // replaces the current entry with its final target.\n return resolveEntry(router, toLocation(router, to)).then((entry) =>\n commitReplace(router, entry.task, entry.location)\n );\n }\n return navigate(router, to);\n}\n\n/**\n * Read the parsed search params of the current location.\n *\n * The raw `location.search` string is subscribed via\n * `useSyncExternalStore`(same source as {@link useSearchParams}), so every\n * location change(push, replace or pop) re-renders the component with the\n * latest params; the parse itself runs on each render.\n *\n * Without a schema the hook degrades to the raw input object of\n * `parseSearchInput` — strings, arrays for repeated keys\n * (`{page: '2', tag: ['a', 'b']}`). With a schema — any zod/valibot/\n * arktype schema, see the route {@link Route.search search field} — the\n * returned object is the schema's parsed output, so coercion and defaults\n * apply, e.g. `useSearch(pageSchema).page` is a number.\n *\n * Prefer declaring the schema once on the route: its `data` loader then\n * receives a parsed `ctx.search` during resolve, and an invalid search\n * fails the navigation through the existing error channels instead of\n * throwing during render.\n *\n * @group Hooks\n * @param schema an optional Standard Schema validator of the search; it\n * must validate synchronously\n * @returns the parsed search params of the current location\n * @throws {SearchError} when `schema` rejects the current search\n */\nexport function useSearch<S extends StandardSchemaV1>(\n schema: S\n): SearchOutputOf<S>;\nexport function useSearch(): SearchInput;\nexport function useSearch(schema?: StandardSchemaV1): unknown {\n const router = useRouter();\n\n // Subscribe to the raw history(not the core view listener): search must\n // update on ANY location change, including same-view re-resolves.\n const subscribe = useCallback(\n (onStoreChange: () => void) => router.history.listen(() => onStoreChange()),\n [router]\n );\n // Snapshot the raw string(never a parsed object) to keep the snapshot\n // reference stable between renders.\n const getSnapshot = useCallback(\n () => router.history.location.search,\n [router]\n );\n // There is no meaningful search during SSR.\n const getServerSnapshot = useCallback(() => '', []);\n\n const search = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getServerSnapshot\n );\n\n return schema ? parseSearchSync(schema, search) : parseSearchInput(search);\n}\n"],"mappings":"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"}
|
package/dist/server.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-C-Bgsx5W.cjs");exports.resolveServerView=e.resolveServerView;
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as r}from"./ssr-
|
|
1
|
+
import{n as r}from"./ssr-873l9jA-.js";export{r as resolveServerView};
|
|
@@ -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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"}
|
|
@@ -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,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,10 @@ 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';
|
|
8
10
|
export { default as defaultResolveView } from './resolve-view';
|
|
9
11
|
export * from './types';
|
|
10
12
|
export { hydrate } from './ssr';
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -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
package/dist/ssr-Cow2_ikA.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
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 c(){return(0,e.useContext)(i)}var s=(0,e.createContext)(null);function l(){const r=c(),t=(0,e.useContext)(s);return r??t}var d=(0,e.createContext)([void 0,{}]);function f(){return(0,e.useContext)(d)}function x(){return f()[1]}function p({children:r,name:n,data:o}){const u=x(),i=(0,e.useMemo)(()=>[o,n?{...u,[n]:o}:u],[o,n,u]);return(0,t.jsx)(d.Provider,{value:i,children:r})}var h=(0,e.createContext)(void 0);function m(){return(0,e.useContext)(h)}function v(e){const[r,t]=f();return e?t[e]:r}var y=(0,e.createContext)(void 0);function b(){return(0,e.useContext)(y)}function g(e,r){return C(e,r,(e,r)=>e?.(r))}var j=new WeakMap;function P(e,r){const t=new Array(e.length);return C(e,r,(e,r)=>Promise.resolve(e?.(r)).then(e=>t[r.index]=e)).then(e=>(j.set(e,t),e))}function C(e,{router:r,location:o,signal:u},i){return Promise.all(e.map(({route:a},c)=>{const s={matched:e,params:(0,n.mergeMatchedParams)(e,c),index:c,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=>(s.search=e,i(a.data,s))):i(a.data,s),function(){if(!a.component)return l;const e=a.component(s);return Promise.resolve(e).then(e=>"default"in e?e.default:e)}()]).then(([e,r])=>(0,t.jsx)(p,{data:e,name:a.name,children:(0,t.jsx)(h.Provider,{value:s,children:(0,t.jsx)(r,{})})}),e=>{if(!a.errorComponent)throw e;return(0,t.jsx)(p,{data:void 0,name:a.name,children:(0,t.jsx)(h.Provider,{value:s,children:(0,t.jsx)(a.errorComponent,{error:e,ctx:s})})})})})).then(e=>e.reverse().reduce((e,r)=>(0,t.jsx)(a,{value:e,children:r})))}var w=(0,e.createContext)(null);function O({router:r,children:o}){const i=(0,e.useRef)((0,n.getCurrentView)(r)),c=(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)(c,l,d),x=(0,e.useContext)(y),p=null==f&&"pending"===x?.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,x.key):null;return(0,t.jsx)(w.Provider,{value:r,children:void 0===o?f??p:(0,t.jsx)(a,{value:f,children:(0,t.jsx)(s.Provider,{value:p,children:o})})})}function R(e,r,{resolveView:t=g,...o}={}){return(0,n.create)(e,r,t,o)}function M({routes:r,children:u,...i},a){const[c,s]=(0,o.splitProps)(i,["baseUrl","currentView"]),{baseUrl:l,currentView:d}=c,[f,x]=(0,e.useState)(),p=(0,e.useMemo)(()=>R(r,a(),{...i,onLoadingChange(e){x(e&&{key:(0,o.uniqId)(),status:e})}}),[r,a,l,d]);(0,e.useEffect)(()=>{(0,n.setOptions)(p,{...s,onLoadingChange(e){x(e&&{key:(0,o.uniqId)(),status:e})}})},[p,s]);const h=(0,e.useMemo)(()=>(0,t.jsx)(O,{router:p,children:u}),[p,u]);return(0,t.jsx)(y.Provider,{value:f,children:h})}function S(e){return M(e,r.createBrowserHistory)}function H(e){return M(e,r.createHashHistory)}function V({initialEntries:t,initialIndex:n,...o}){return M(o,(0,e.useMemo)(()=>()=>(0,r.createMemoryHistory)({initialEntries:t,initialIndex:n}),[t,n]))}function q(){const r=(0,e.useContext)(w);if(!r)throw new Error("useRouter() must be used within a <Router> component");return r}var k="_nativeRouterReactSSRData";function E(e,r,o){return(0,n.resolve)(e,r).then(n=>{const u=function(e){return j.get(e)}(n),i=e.history.location.state?.index||0;return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(O,{router:e,children:n}),(0,t.jsx)("script",{...o?.scriptAttributes,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`window.${o?.hydrateKey||k} = ${a={data:u,location:r,index:i},JSON.stringify(a).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")};`}})]});var a})}function I(e,t,{scriptAttributes:u,hydrateKey:i,...a}={}){const c=(0,n.create)(e,(0,r.createMemoryHistory)({initialEntries:[t]}),P,a);return E(c,(0,o.isString)(t)?(0,n.toLocation)(c,t):t,{scriptAttributes:u,hydrateKey:i})}function A(e,t){const{data:o,location:u,index:i=0}=window[t?.hydrateKey||k],a=(0,r.createBrowserHistory)();a.replace((0,r.createPath)(a.location),{index:i});const c=(0,n.create)(e,a,function(e){return(r,t)=>C(r,t,(r,t)=>e[t.index])}(o),t);return(0,n.resolve)(c,u).then(e=>({view:e,router:c}))}Object.defineProperty(exports,"HashRouter",{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,"HistoryRouter",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"MemoryRouter",{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,"Router",{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,"View",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"createRouter",{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,"hydrate",{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,"resolveServerView",{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,"resolveView",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"useData",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"useLoading",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"useMatched",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"useNamedData",{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,"useRouter",{enumerable:!0,get:function(){return q}}),Object.defineProperty(exports,"useView",{enumerable:!0,get:function(){return c}});
|
|
2
|
-
//# sourceMappingURL=ssr-Cow2_ikA.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-Cow2_ikA.cjs","names":[],"sources":["../src/context.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 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';\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 <DataProvider data={data} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <C />\n </MatchedContext.Provider>\n </DataProvider>\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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"}
|
package/dist/ssr-j5c5cH-B.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{createContext as r,useCallback as n,useContext as e,useEffect as t,useMemo as i,useRef as o,useState as a}from"react";import{createBrowserHistory as u,createHashHistory as c,createMemoryHistory as s,createPath as l}from"history";import{Fragment as d,jsx as h,jsxs as f}from"react/jsx-runtime";import{create as v,getCurrentView as m,listen as p,match as g,mergeMatchedParams as y,parseSearch as w,parseSearchInput as x,resolve as P,setOptions as b,toLocation as C}from"@native-router/core";import{isString as A,splitProps as R,uniqId as k}from"@native-router/core/util";import{useSyncExternalStore as E}from"use-sync-external-store/shim";var K=r(null);function S(r){/* @__PURE__ */
|
|
2
|
-
return h(K.Provider,{...r})}function I(){return e(K)}var L=r(null);function V(){const r=I(),n=e(L);return r??n}var _=r([void 0,{}]);function H(){return e(_)}function M(){return H()[1]}function U({children:r,name:n,data:e}){const t=M(),o=i(()=>[e,n?{...t,[n]:e}:t],[e,n,t]);/* @__PURE__ */
|
|
3
|
-
return h(_.Provider,{value:o,children:r})}var W=r(void 0);function $(){return e(W)}function j(r){const[n,e]=H();return r?e[r]:n}var D=r(void 0);function J(){return e(D)}function N(r,n){return q(r,n,(r,n)=>r?.(n))}var O=/* @__PURE__ */new WeakMap;function T(r,n){const e=new Array(r.length);return q(r,n,(r,n)=>Promise.resolve(r?.(n)).then(r=>e[n.index]=r)).then(r=>(O.set(r,e),r))}function q(r,{router:n,location:e,signal:t},i){return Promise.all(r.map(({route:o},a)=>{const u={matched:r,params:y(r,a),index:a,router:n,location:e,search:x(e.search),signal:t??(new AbortController).signal};return Promise.all([o.search?w(o.search,e.search).then(r=>(u.search=r,i(o.data,u))):i(o.data,u),function(){if(!o.component)return V;const r=o.component(u);return Promise.resolve(r).then(r=>"default"in r?r.default:r)}()]).then(([r,n])=>/* @__PURE__ */h(U,{data:r,name:o.name,children:/* @__PURE__ */h(W.Provider,{value:u,children:/* @__PURE__ */h(n,{})})}),r=>{if(!o.errorComponent)throw r;/* @__PURE__ */
|
|
4
|
-
return h(U,{data:void 0,name:o.name,children:/* @__PURE__ */h(W.Provider,{value:u,children:/* @__PURE__ */h(o.errorComponent,{error:r,ctx:u})})})})})).then(r=>r.reverse().reduce((r,n)=>/* @__PURE__ */h(S,{value:r,children:n})))}var z=r(null);function B({router:r,children:t}){const i=o(m(r)),a=n(n=>p(r,r=>{i.current=r,n()}),[r]),u=n(()=>i.current,[]),c=n(()=>m(r),[r]),s=E(a,u,c),l=e(D),d=null==s&&"pending"===l?.status?function(r,n){const{resolving:e}=r,t=e?g(r,e.pathname):void 0;if(!t)return null;for(let i=t.length-1;i>=0;i--){const r=t[i].route.pendingComponent;if(r)/* @__PURE__ */return h(r,{},n)}return null}(r,l.key):null;/* @__PURE__ */
|
|
5
|
-
return h(z.Provider,{value:r,children:void 0===t?s??d:/* @__PURE__ */h(S,{value:s,children:/* @__PURE__ */h(L.Provider,{value:d,children:t})})})}function F(r,n,{resolveView:e=N,...t}={}){return v(r,n,e,t)}function G({routes:r,children:n,...e},o){const[u,c]=R(e,["baseUrl","currentView"]),{baseUrl:s,currentView:l}=u,[d,f]=a(),v=i(()=>F(r,o(),{...e,onLoadingChange(r){f(r&&{key:k(),status:r})}}),[r,o,s,l]);t(()=>{b(v,{...c,onLoadingChange(r){f(r&&{key:k(),status:r})}})},[v,c]);const m=i(()=>/* @__PURE__ */h(B,{router:v,children:n}),[v,n]);/* @__PURE__ */
|
|
6
|
-
return h(D.Provider,{value:d,children:m})}function Q(r){return G(r,u)}function X(r){return G(r,c)}function Y({initialEntries:r,initialIndex:n,...e}){return G(e,i(()=>()=>s({initialEntries:r,initialIndex:n}),[r,n]))}function Z(){const r=e(z);if(!r)throw new Error("useRouter() must be used within a <Router> component");return r}var rr="_nativeRouterReactSSRData";function nr(r,n,e){return P(r,n).then(t=>{const i=function(r){return O.get(r)}(t),o=r.history.location.state?.index||0;/* @__PURE__ */
|
|
7
|
-
return f(d,{children:[/* @__PURE__ */h(B,{router:r,children:t}),/* @__PURE__ */h("script",{...e?.scriptAttributes,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`window.${e?.hydrateKey||rr} = ${a={data:i,location:n,index:o},JSON.stringify(a).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")};`}})]});var a})}function er(r,n,{scriptAttributes:e,hydrateKey:t,...i}={}){const o=v(r,s({initialEntries:[n]}),T,i);return nr(o,A(n)?C(o,n):n,{scriptAttributes:e,hydrateKey:t})}function tr(r,n){const{data:e,location:t,index:i=0}=window[n?.hydrateKey||rr],o=u();o.replace(l(o.location),{index:i});const a=v(r,o,function(r){return(n,e)=>q(n,e,(n,e)=>r[e.index])}(e),n);return P(a,t).then(r=>({view:r,router:a}))}export{Y as a,Z as c,j as d,J as f,I as h,Q as i,N as l,M as m,er as n,B as o,$ as p,X as r,F as s,tr as t,V as u};
|
|
8
|
-
//# sourceMappingURL=ssr-j5c5cH-B.js.map
|
package/dist/ssr-j5c5cH-B.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-j5c5cH-B.js","names":[],"sources":["../src/context.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 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';\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 <DataProvider data={data} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <C />\n </MatchedContext.Provider>\n </DataProvider>\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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"}
|