@native-router/react 1.1.2 → 1.2.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 CHANGED
@@ -9,14 +9,97 @@
9
9
 
10
10
  English | [简体中文](./README-zh_CN.md)
11
11
 
12
+ ## Highlights
13
+
14
+ ### Back with zero requests
15
+
16
+ Every committed navigation stores its resolved view in the router's in-memory view stack. Back/forward lands on the cached view instantly — the route is not re-matched and `data` is not refetched.
17
+
18
+ ```tsx
19
+ import {useRouter} from '@native-router/react';
20
+ import {back} from '@native-router/core';
21
+
22
+ function BackButton() {
23
+ const router = useRouter();
24
+ // Renders the cached view of the previous entry instantly
25
+ return <button onClick={() => back(router)}>Back</button>;
26
+ }
27
+ ```
28
+
29
+ ### Survives a refresh
30
+
31
+ The session stack is serialized into `history.state` as a bounded tail window (`maxStackDepth`, default 100) and restored on startup. Warm the window once after a refresh with `initHistoryStack`, and every in-window back/forward renders from cache with zero requests. Entries outside the window fall back to a single lazy re-resolve.
32
+
33
+ ```tsx
34
+ import {useEffect} from 'react';
35
+ import {useRouter} from '@native-router/react';
36
+ import {initHistoryStack} from '@native-router/core';
37
+
38
+ function StackWarmer() {
39
+ const router = useRouter();
40
+ useEffect(() => {
41
+ // Warm up the window restored from history.state after a refresh
42
+ initHistoryStack(router);
43
+ }, [router]);
44
+ return null;
45
+ }
46
+ ```
47
+
48
+ ### Prefetch and preview
49
+
50
+ `PrefetchLink` resolves the target view — through the route guards — before the click, with four strategies: `intent` (default, hover/focus), `render`, `viewport` and `none`. `usePrefetch` exposes `{view, loading, error}`, so a popover can render a live preview of the target view while the user is still hovering.
51
+
52
+ ```tsx
53
+ import {PrefetchLink, usePrefetch} from '@native-router/react';
54
+
55
+ function Preview({visible}: {visible: boolean}) {
56
+ const {view, loading, error} = usePrefetch();
57
+ if (!visible) return null;
58
+ if (loading) return <div className="popover">Loading…</div>;
59
+ if (error) return <div className="popover">Failed to prefetch</div>;
60
+ return <div className="popover">{view}</div>; // the target view, before any click
61
+ }
62
+
63
+ <PrefetchLink to="/users/1" prefetch="viewport">
64
+ User 1
65
+ <Preview visible={false /* show on hover */} />
66
+ </PrefetchLink>
67
+ ```
68
+
12
69
  ## Features
13
70
 
14
- - Asynchronous navigation
15
- - Cancelable
16
- - Page data concurrent fetch
17
- - Link prefetch and preview
18
- - Most unused features can be tree-shaking
19
- - SSR support
71
+ - Three history modes out of the box: `HistoryRouter`, `HashRouter`, `MemoryRouter` (tests, widgets); `Router` renders with an externally created instance, `createRouter` builds one for a custom history
72
+ - Route guards: static `redirect` and async `beforeLoad` on every route level, run shallow → deep; more than 10 chained redirects reject with `RedirectLoopError`
73
+ - Cancelable async navigation: starting a new navigation supersedes the in-flight one; `cancel(router)` aborts it; a history POP cancels it too
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}`
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
+ - `ScrollRestoration` restores the scroll offset per history entry on back/forward and resets it on push (`resetOnPush` to opt out)
78
+ - 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}`
81
+ - 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
82
+ - Tree-shakable: `sideEffects: false` — unused components and hooks drop out of the bundle
83
+
84
+ ## Matching semantics
85
+
86
+ - Routes match in **declaration order** and the first match wins — there is no sorting by specificity.
87
+ - A route **without `path`** is a layout: it matches the empty prefix and its children are matched against the full remaining path.
88
+ - A leaf child with **`path: ''`** matches whatever is left under its parent. Declared after its concrete siblings it serves as the parent's index route (and as the fallback for paths unmatched under the parent).
89
+ - **Trailing slashes are significant**: `/users/` does not match `/users`.
90
+ - Matching is **case-sensitive**.
91
+ - Params of nested levels are merged **deep over shallow** (`mergeMatchedParams`): for `/:id` + `/posts/:id`, the deeper `id` wins.
92
+
93
+ ## Link interception
94
+
95
+ `Link` (and `PrefetchLink`/`NavLink`, which delegate to it) intercepts only plain primary-button clicks. The browser keeps its default behavior for:
96
+
97
+ - modified clicks (⌘/Ctrl/Shift/Alt) and any non-left button
98
+ - `target="_blank"`, `target="_parent"` or `target="_top"`
99
+ - `rel` containing `external`
100
+ - events already `defaultPrevented`
101
+
102
+ While a navigation started by a link is pending, further clicks on that link are ignored.
20
103
 
21
104
  ## Install
22
105
 
@@ -24,44 +107,51 @@ English | [简体中文](./README-zh_CN.md)
24
107
  npm i @native-router/react
25
108
  ```
26
109
 
110
+ `@native-router/core` comes along as a dependency.
111
+
27
112
  ## Usage
28
113
 
29
114
  ```tsx
30
115
  import {View, HistoryRouter as Router} from '@native-router/react';
116
+ import type {Route} from '@native-router/react';
31
117
  import Loading from '@/components/Loading';
32
118
  import RouterError from '@/components/RouterError';
33
119
  import * as userService from '@/services/user';
34
120
 
121
+ const routes = {
122
+ component: () => import('./Layout'), // a layout renders <View /> for its child
123
+ children: [
124
+ {
125
+ path: '/',
126
+ component: () => import('./Home')
127
+ },
128
+ {
129
+ path: '/users',
130
+ component: () => import('./UserList'),
131
+ data: userService.fetchList
132
+ },
133
+ {
134
+ path: '/users/:id',
135
+ component: () => import('./UserProfile'),
136
+ // guards run before the view resolves; return a path to redirect
137
+ async beforeLoad({params}) {
138
+ if (!await canView(+params.id)) return '/login';
139
+ },
140
+ // data receives {matched, index, router, location, params}
141
+ data: ({params}) => userService.fetchById(+params.id),
142
+ errorComponent: ({error}) => <p>{error.message}</p>
143
+ },
144
+ {
145
+ path: '/help',
146
+ component: () => import('./Help')
147
+ }
148
+ ]
149
+ } as Route;
150
+
35
151
  export default function App() {
36
152
  return (
37
153
  <Router
38
- routes={{
39
- component: () => import('./Layout'),
40
- children: [
41
- {
42
- path: '/',
43
- component: () => import('./Home')
44
- },
45
- {
46
- path: '/users',
47
- component: () => import('./UserList'),
48
- data: userService.fetchList
49
- },
50
- {
51
- path: '/users/:id',
52
- component: () => import('./UserProfile'),
53
- data: ({id}) => userService.fetchById(+id)
54
- },
55
- {
56
- path: '/help',
57
- component: () => import('./Help')
58
- },
59
- {
60
- path: '/about',
61
- component: () => import('./About')
62
- }
63
- ]
64
- }}
154
+ routes={routes}
65
155
  baseUrl="/demos"
66
156
  errorHandler={(e) => <RouterError error={e} />}
67
157
  >
@@ -70,25 +160,107 @@ export default function App() {
70
160
  </Router>
71
161
  );
72
162
  }
163
+ ```
164
+
165
+ Read the page data and params in a view:
166
+
167
+ ```tsx
168
+ import {useData, useMatched} from '@native-router/react';
169
+
170
+ export default function UserProfile() {
171
+ const user = useData<User>(); // the data of the current level, typed
172
+ const {params} = useMatched(); // params accumulated to this level
173
+ return <h1>{user!.username}(#{params.id})</h1>;
174
+ }
175
+ ```
176
+
177
+ The progress bar above the view is just `useLoading`:
73
178
 
179
+ ```tsx
180
+ import {useLoading} from '@native-router/react';
181
+
182
+ export default function Loading() {
183
+ const loading = useLoading();
184
+ return loading?.status === 'pending' ? <div className="bar" /> : null;
185
+ }
74
186
  ```
75
- See [demos](/demos/) for a complete example.
76
187
 
77
- ## Documentation
188
+ Read and write the query string:
78
189
 
79
- [API](https://native-router.github.io/react/modules.html)
190
+ ```tsx
191
+ import {useSearchParams} from '@native-router/react';
80
192
 
81
- import { Router } from '@native-router/react';
193
+ function Pager() {
194
+ const [searchParams, setSearchParams] = useSearchParams();
195
+ const page = searchParams.get('page') ?? '1';
82
196
 
83
- const routes = [
84
- {
85
- path: '/admin',
86
- children: [...]
197
+ function go(next: number) {
198
+ const params = new URLSearchParams(searchParams);
199
+ params.set('page', String(next));
200
+ setSearchParams(params); // push by default
201
+ // setSearchParams(params, {replace: true}); // or rewrite the current entry
87
202
  }
88
- ];
89
203
 
90
- function App() {
91
- return <Router routes={routes} />;
204
+ return <button onClick={() => go(+page + 1)}>Next</button>;
205
+ }
206
+ ```
207
+
208
+ Restore the scroll offset like a native app (place inside the Router):
209
+
210
+ ```tsx
211
+ import {ScrollRestoration} from '@native-router/react';
212
+
213
+ // in your layout:
214
+ <ScrollRestoration /> // back/forward restore, push resets; resetOnPush={false} to keep
215
+ ```
216
+
217
+ On mount it also sets `history.scrollRestoration` to `manual`: the browser's own `auto` restoration would race the component's restore and pre-scroll while the left entry's offset is still being read, so the component owns scroll restoration for the session (the setting is not reverted on unmount).
218
+
219
+ Validate and type the search with a schema — any zod/valibot/arktype schema works, the router only speaks [Standard Schema](https://standardschema.dev). Declare it once on the route and the search is parsed during resolve: the `data` loader receives a typed `ctx.search` (coerced numbers, defaults applied), and an invalid search fails the level through the existing error layers — the route `errorComponent`, else the global `errorHandler`.
220
+
221
+ ```tsx
222
+ import {useData, useSearch} from '@native-router/react';
223
+ import type {Route} from '@native-router/react';
224
+ import {z} from 'zod';
225
+
226
+ const listSearch = z.object({
227
+ page: z.coerce.number().default(1),
228
+ tag: z.string().optional()
229
+ });
230
+
231
+ const listRoute = {
232
+ path: '/articles',
233
+ search: listSearch,
234
+ component: () => import('./ArticleList'),
235
+ // ctx.search: {page: number; tag?: string} — parsed and typed
236
+ data: ({search}) => fetchArticles(search.page, search.tag),
237
+ errorComponent: ({error}) => <p>{error.message}</p>
238
+ } as Route<'/articles', {page: number; tag?: string}>;
239
+
240
+ function ArticleList() {
241
+ const articles = useData<Article[]>(); // typed, no casts
242
+ const {page} = useSearch(listSearch); // parsed like ctx.search
243
+ const raw = useSearch(); // degraded raw object: {page: '2'} strings
244
+ // ...
92
245
  }
93
246
  ```
94
247
 
248
+ `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.
249
+
250
+ See [demos](./demos) for a complete example.
251
+
252
+ ## Development
253
+
254
+ `@native-router/react` (this package) and `@native-router/core` live in **two independent repositories**; clone them side by side. The vitest config aliases `@native-router/core` to `../core/src`, so tests exercise the latest core source without any install-level linking (a `@native-router/core` from the npm registry is still installed for types and production builds).
255
+
256
+ ```bash
257
+ pnpm install
258
+ pnpm start # demo dev server
259
+ pnpm test:run # react tests
260
+ ```
261
+
262
+ React's type check and production build resolve core from the npm registry, so publish core first when this repo needs to consume unpublished core APIs.
263
+
264
+ ## Documentation
265
+
266
+ [API](https://native-router.github.io/react/modules.html)
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-CsvbhYZu.cjs"),t=require("@native-router/core"),r=require("react"),o=require("react/jsx-runtime");const u=r.createContext({loading:!1});exports.HashRouter=e.HashRouter,exports.HistoryRouter=e.HistoryRouter,exports.MemoryRouter=e.MemoryRouter,exports.Router=e.Router,exports.View=e.View,exports.createRouter=e.createRouter,exports.defaultResolveView=e.resolveView,exports.resolveClientView=e.resolveClientView,exports.useData=e.useData,exports.useLoading=e.useLoading,exports.useMatched=e.useMatched,exports.useRouter=e.useRouter,exports.useView=e.useView,exports.Link=function({to:u,...s}){const n=e.useRouter(),i=r.useRef(!1);return o.jsx("a",{...s,href:t.createHref(n,u),onClick:function(e){e.preventDefault(),i.current||(i.current=!0,t.navigate(n,u).finally(()=>i.current=!1))}})},exports.PrefetchLink=function({to:s,children:n,...i}){const c=e.useRouter(),a=r.useRef(void 0),[l,f]=r.useState(!1),[x,p]=r.useState(),[R,d]=r.useState(),v=t.toLocation(c,s);function h(){f(!0),a.current=t.resolve(c,v).then(e=>(d(e),e)).catch(e=>{throw p(e),e}).finally(()=>f(!1))}r.useEffect(()=>{a.current=void 0},[s,c]);const w=r.useMemo(()=>({loading:l,error:x,view:R}),[l,x,R]);return o.jsx(u.Provider,{value:w,children:o.jsx("a",{...i,href:t.createHref(c,s),onMouseEnter:function(){a.current||h()},onClick:function(e){e.preventDefault(),a.current||h(),t.commit(c,a.current,v).finally(()=>a.current=void 0)},children:n})})},exports.usePrefetch=function(){return r.useContext(u)};
2
- //# sourceMappingURL=index.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-BVFDJcr7.cjs");let t=require("react"),r=require("history"),o=require("react/jsx-runtime"),n=require("@native-router/core"),s=require("use-sync-external-store/shim");function u(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 c({to:r,...s}){const c=e.useRouter(),a=(0,t.useRef)(!1);return(0,o.jsx)("a",{...s,href:(0,n.createHref)(c,r),onClick:function(e){u(e,s.target,s.rel)&&(e.preventDefault(),a.current||(a.current=!0,(0,n.navigate)(c,r).catch(()=>{}).finally(()=>{a.current=!1})))}})}var a=(0,t.createContext)({loading:!1});exports.HashRouter=e.HashRouter,exports.HistoryRouter=e.HistoryRouter,exports.Link=c,exports.MemoryRouter=e.MemoryRouter,exports.NavLink=function({to:r,end:u=!1,caseSensitive:a=!1,className:i,style:l,ariaCurrent:f,children:h,...d}){const p=e.useRouter(),y=(0,t.useCallback)(e=>p.history.listen(()=>e()),[p]),x=(0,t.useCallback)(()=>p.history.location.pathname,[p]),R=(0,s.useSyncExternalStore)(y,x,x),v=(0,n.toLocation)(p,r).pathname,[w,m]=a?[R,v]:[R.toLowerCase(),v.toLowerCase()],S=w===m,k=u||S?S:w.startsWith(m.endsWith("/")?m:`${m}/`),b={isActive:k,isExactActive:S};return(0,o.jsx)(c,{to:r,...d,className:"function"==typeof i?i(b):i,style:"function"==typeof l?l(b):l,"aria-current":k?f??"page":void 0,children:"function"==typeof h?h(b):h})},exports.PrefetchLink=function({to:r,prefetch:s="intent",children:c,...i}){const l=e.useRouter(),f=(0,t.useRef)(null),h=(0,t.useRef)(void 0),d=(0,t.useRef)(!1),[p,y]=(0,t.useState)(!1),[x,R]=(0,t.useState)(),[v,w]=(0,t.useState)();function m(){y(!0),R(void 0),d.current=!1;const e=(0,n.preload)(l,r);return h.current=e,e.then(e=>e.task).then(e=>w(e),e=>{R(e),d.current=!0}).finally(()=>y(!1)),e}function S(){h.current||m()}(0,t.useEffect)(()=>{h.current=void 0,d.current=!1,y(!1),R(void 0),w(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:p,error:x,view:v}),[p,x,v]),b="intent"===s?{onMouseEnter:S,onFocus:S}:void 0;return(0,o.jsx)(a.Provider,{value:k,children:(0,o.jsx)("a",{...i,...b,ref:f,href:(0,n.createHref)(l,r),onClick:function(e){if(!u(e,i.target,i.rel))return;e.preventDefault();const t=h.current;(!t||d.current?m():t).then(e=>(0,n.commit)(l,e.task,e.location)).catch(()=>{}).finally(()=>{h.current=void 0,d.current=!1})},children:c})})},exports.Router=e.Router,exports.ScrollRestoration=function({resetOnPush:o=!0}){const n=e.useRouter(),s=(0,t.useRef)(new Map),u=(0,t.useRef)(-1),c=(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;u.current=t(n.history.location.state),c.current=(0,r.createPath)(n.history.location);let a=!1,i=!1,l=!0;const f=n.history.listen(({action:s})=>{i||="POP"===s,a||(a=!0,queueMicrotask(()=>{if(a=!1,!l)return;const s=i;i=!1;const{location:f}=n.history,h=t(f.state),d=(0,r.createPath)(f),p=u.current;if(h!==p&&e.set(p,{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!o||h===p&&d===c.current||(window.scrollTo(0,0),e.set(h,{x:0,y:0}));u.current=h,c.current=d}))});return()=>{l=!1,f()}},[n,o]),null},exports.View=e.View,exports.createRouter=e.createRouter,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)(a)},exports.useRouter=e.useRouter,exports.useSearch=function(r){const o=e.useRouter(),u=(0,t.useCallback)(e=>o.history.listen(()=>e()),[o]),c=(0,t.useCallback)(()=>o.history.location.search,[o]),a=(0,t.useCallback)(()=>"",[]),i=(0,s.useSyncExternalStore)(u,c,a);return r?(0,n.parseSearchSync)(r,i):(0,n.parseSearchInput)(i)},exports.useSearchParams=function(){const r=e.useRouter(),o=(0,t.useCallback)(e=>r.history.listen(()=>e()),[r]),u=(0,t.useCallback)(()=>r.history.location.search,[r]),c=(0,t.useCallback)(()=>"",[]);return[new URLSearchParams((0,s.useSyncExternalStore)(o,u,c)),(0,t.useCallback)((e,t)=>{const{history:o}=r,s=("function"==typeof e?e(new URLSearchParams(o.location.search)):e).toString(),{pathname:u,hash:c}=o.location,a=u+(s?`?${s}`:"")+c;return t?.replace?(0,n.resolveEntry)(r,(0,n.toLocation)(r,a)).then(e=>(0,n.commitReplace)(r,e.task,e.location)):(0,n.navigate)(r,a)},[r])]},exports.useView=e.useView;
2
+ //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/components/PrefetchLink.tsx","../src/components/Link.tsx"],"sourcesContent":["import {commit, createHref, resolve, toLocation} from '@native-router/core';\nimport type {LinkProps} 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';\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 support hover prefetch.\n * @param props\n * @group Components\n */\nexport default function PrefetchLink({to, children, ...rest}: LinkProps) {\n const router = useRouter();\n const viewPromiseRef = useRef<undefined | Promise<ReactNode>>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error>();\n const [view, setView] = useState<ReactNode>();\n const location = toLocation(router, to);\n\n function prefetchIt() {\n setLoading(true);\n viewPromiseRef.current = resolve(router, location)\n .then((v) => {\n setView(v);\n return v;\n })\n .catch((e) => {\n setError(e);\n throw e;\n })\n .finally(() => setLoading(false));\n }\n\n function handlePrefetch() {\n if (viewPromiseRef.current) return;\n prefetchIt();\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n e.preventDefault();\n if (!viewPromiseRef.current) {\n prefetchIt();\n }\n commit(router, viewPromiseRef.current!, location).finally(\n () => (viewPromiseRef.current = undefined)\n );\n }\n\n useEffect(() => {\n viewPromiseRef.current = undefined;\n }, [to, router]);\n\n const linkContext = useMemo(\n () => ({loading, error, view}),\n [loading, error, view]\n );\n\n return (\n <Context.Provider value={linkContext}>\n <a\n {...rest}\n href={createHref(router, to)}\n onMouseEnter={handlePrefetch}\n onClick={handleClick}\n >\n {children}\n </a>\n </Context.Provider>\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';\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 lock = useRef(false);\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n e.preventDefault();\n\n if (lock.current) return;\n lock.current = true;\n navigate(router, to).finally(() => (lock.current = false));\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"],"names":["Context","createContext","loading","to","rest","router","useRouter","lock","useRef","href","createHref","onClick","e","preventDefault","current","navigate","finally","children","viewPromiseRef","setLoading","useState","error","setError","view","setView","location","toLocation","prefetchIt","resolve","then","v","catch","useEffect","undefined","linkContext","useMemo","_jsx","Provider","value","onMouseEnter","commit","useContext"],"mappings":"yMAgBA,MAAMA,EAAUC,EAAAA,cAAmC,CAACC,SAAS,obCN7D,UAA6BC,GAACA,KAAOC,IACnC,MAAMC,EAASC,EAAAA,YACTC,EAAOC,EAAAA,QAAO,GASpB,aAEE,IAAA,IAAOJ,EAAMK,KAAMC,EAAAA,WAAWL,EAAQF,GAAKQ,QAT7C,SAAqBC,GACnBA,EAAEC,iBAEEN,EAAKO,UACTP,EAAKO,SAAU,EACfC,EAAAA,SAASV,EAAQF,GAAIa,QAAQ,IAAOT,EAAKO,SAAU,GACrD,GAKF,uBDMA,UAAqCX,GAACA,EAAAA,SAAIc,KAAab,IACrD,MAAMC,EAASC,EAAAA,YACTY,EAAiBV,EAAAA,gBAChBN,EAASiB,GAAcC,EAAAA,UAAS,IAChCC,EAAOC,GAAYF,cACnBG,EAAMC,GAAWJ,aAClBK,EAAWC,EAAAA,WAAWrB,EAAQF,GAEpC,SAASwB,IACPR,GAAW,GACXD,EAAeJ,QAAUc,UAAQvB,EAAQoB,GACtCI,KAAMC,IACLN,EAAQM,GACDA,IAERC,MAAOnB,IAEN,MADAU,EAASV,GACHA,IAEPI,QAAQ,IAAMG,GAAW,GAC9B,CAiBAa,EAAAA,UAAU,KACRd,EAAeJ,aAAUmB,GACxB,CAAC9B,EAAIE,IAER,MAAM6B,EAAcC,EAAAA,QAClB,KAAA,CAAQjC,UAASmB,QAAOE,SACxB,CAACrB,EAASmB,EAAOE,IAGnB,OACEa,EAAAA,IAACpC,EAAQqC,SAAQ,CAACC,MAAOJ,EAAYjB,eACnC,IAAA,IACMb,EACJK,KAAMC,EAAAA,WAAWL,EAAQF,GACzBoC,aA7BN,WACMrB,EAAeJ,SACnBa,GACF,EA2BMhB,QAzBN,SAAqBC,GACnBA,EAAEC,iBACGK,EAAeJ,SAClBa,IAEFa,SAAOnC,EAAQa,EAAeJ,QAAUW,GAAUT,QAChD,IAAOE,EAAeJ,eAE1B,EAiB2BG,cAM7B,sBAnEO,WACL,OAAOwB,EAAAA,WAAWzC,EACpB"}
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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"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import{u as r}from"./ssr-CiHFLO3B.js";export{H as HashRouter,a as HistoryRouter,M as MemoryRouter,R as Router,V as View,c as createRouter,b as defaultResolveView,d as resolveClientView,e as useData,f as useLoading,g as useMatched,h as useView}from"./ssr-CiHFLO3B.js";import{createHref as t,navigate as n,toLocation as o,commit as u,resolve as i}from"@native-router/core";import{useRef as s,useState as l,useEffect as m,useMemo as v,createContext as p,useContext as w}from"react";import{jsx as y}from"react/jsx-runtime";function j({to:e,...o}){const u=r(),a=s(!1);
2
- /* @__PURE__ */
3
- return y("a",{...o,href:t(u,e),onClick:function(r){r.preventDefault(),a.current||(a.current=!0,n(u,e).finally(()=>a.current=!1))}})}const x=/* @__PURE__ */p({loading:!1});function C(){return w(x)}function D({to:e,children:n,...a}){const c=r(),f=s(void 0),[d,h]=l(!1),[p,R]=l(),[w,V]=l(),g=o(c,e);function M(){h(!0),f.current=i(c,g).then(r=>(V(r),r)).catch(r=>{throw R(r),r}).finally(()=>h(!1))}m(()=>{f.current=void 0},[e,c]);const j=v(()=>({loading:d,error:p,view:w}),[d,p,w]);/* @__PURE__ */
4
- return y(x.Provider,{value:j,children:/* @__PURE__ */y("a",{...a,href:t(c,e),onMouseEnter:function(){f.current||M()},onClick:function(r){r.preventDefault(),f.current||M(),u(c,f.current,g).finally(()=>f.current=void 0)},children:n})})}export{j as Link,D as PrefetchLink,C as usePrefetch,r as useRouter};
5
- //# sourceMappingURL=index.js.map
1
+ import{a as t,c as n,d as r,f as e,h as o,i,l as s,m as c,o as a,p as u,r as l,s as f,t as h,u as d}from"./ssr-C17nRDBM.js";import{createContext as y,useCallback as p,useContext as m,useEffect as v,useMemo as w,useRef as x,useState as g}from"react";import{createPath as k}from"history";import{jsx as P}from"react/jsx-runtime";import{commit as b,commitReplace as C,createHref as R,navigate as K,parseSearchInput as L,parseSearchSync as O,preload as S,resolveEntry as I,toLocation as M}from"@native-router/core";import{useSyncExternalStore as _}from"use-sync-external-store/shim";function j(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 A({to:t,...r}){const e=n(),o=x(!1);/* @__PURE__ */
2
+ return P("a",{...r,href:R(e,t),onClick:function(n){j(n,r.target,r.rel)&&(n.preventDefault(),o.current||(o.current=!0,K(e,t).catch(()=>{}).finally(()=>{o.current=!1})))}})}function D({to:t,end:r=!1,caseSensitive:e=!1,className:o,style:i,ariaCurrent:s,children:c,...a}){const u=n(),l=p(t=>u.history.listen(()=>t()),[u]),f=p(()=>u.history.location.pathname,[u]),h=_(l,f,f),d=M(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}/`),x={isActive:w,isExactActive:v};/* @__PURE__ */
3
+ return P(A,{to:t,...a,className:"function"==typeof o?o(x):o,style:"function"==typeof i?i(x):i,"aria-current":w?s??"page":void 0,children:"function"==typeof c?c(x):c})}var E=y({loading:!1});function N(){return m(E)}function T({to:t,prefetch:r="intent",children:e,...o}){const i=n(),s=x(null),c=x(void 0),a=x(!1),[u,l]=g(!1),[f,h]=g(),[d,y]=g();function p(){l(!0),h(void 0),a.current=!1;const n=S(i,t);return c.current=n,n.then(t=>t.task).then(t=>y(t),t=>{h(t),a.current=!0}).finally(()=>l(!1)),n}function m(){c.current||p()}v(()=>{c.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=s.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 k=w(()=>({loading:u,error:f,view:d}),[u,f,d]),C="intent"===r?{onMouseEnter:m,onFocus:m}:void 0;/* @__PURE__ */
4
+ return P(E.Provider,{value:k,children:/* @__PURE__ */P("a",{...o,...C,ref:s,href:R(i,t),onClick:function(t){if(!j(t,o.target,o.rel))return;t.preventDefault();const n=c.current;(!n||a.current?p():n).then(t=>b(i,t.task,t.location)).catch(()=>{}).finally(()=>{c.current=void 0,a.current=!1})},children:e})})}function U({resetOnPush:t=!0}){const r=n(),e=x(/* @__PURE__ */new Map),o=x(-1),i=x("");return v(()=>{if("undefined"==typeof window)return;window.history.scrollRestoration&&(window.history.scrollRestoration="manual");const n=e.current,s=t=>t?.index||0;o.current=s(r.history.location.state),i.current=k(r.history.location);let c=!1,a=!1,u=!0;const l=r.history.listen(({action:e})=>{a||="POP"===e,c||(c=!0,queueMicrotask(()=>{if(c=!1,!u)return;const e=a;a=!1;const{location:l}=r.history,f=s(l.state),h=k(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 W(){const t=n(),r=p(n=>t.history.listen(()=>n()),[t]),e=p(()=>t.history.location.search,[t]),o=p(()=>"",[]);return[new URLSearchParams(_(r,e,o)),p((n,r)=>{const{history:e}=t,o=("function"==typeof n?n(new URLSearchParams(e.location.search)):n).toString(),{pathname:i,hash:s}=e.location,c=i+(o?`?${o}`:"")+s;return r?.replace?I(t,M(t,c)).then(n=>C(t,n.task,n.location)):K(t,c)},[t])]}function $(t){const r=n(),e=p(t=>r.history.listen(()=>t()),[r]),o=p(()=>r.history.location.search,[r]),i=p(()=>"",[]),s=_(e,o,i);return t?O(t,s):L(s)}export{l as HashRouter,i as HistoryRouter,A as Link,t as MemoryRouter,D as NavLink,T as PrefetchLink,a as Router,U as ScrollRestoration,d as View,f as createRouter,s as defaultResolveView,h as hydrate,r as useData,e as useLoading,u as useMatched,c as useNamedData,N as usePrefetch,n as useRouter,$ as useSearch,W as useSearchParams,o as useView};
5
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/components/Link.tsx","../src/components/PrefetchLink.tsx"],"sourcesContent":["import {createHref, navigate} from '@native-router/core';\nimport type {LinkProps} from '@@/types';\nimport {useRef, type MouseEvent} from 'react';\nimport {useRouter} from './Router';\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 lock = useRef(false);\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n e.preventDefault();\n\n if (lock.current) return;\n lock.current = true;\n navigate(router, to).finally(() => (lock.current = false));\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 {commit, createHref, resolve, toLocation} from '@native-router/core';\nimport type {LinkProps} 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';\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 support hover prefetch.\n * @param props\n * @group Components\n */\nexport default function PrefetchLink({to, children, ...rest}: LinkProps) {\n const router = useRouter();\n const viewPromiseRef = useRef<undefined | Promise<ReactNode>>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error>();\n const [view, setView] = useState<ReactNode>();\n const location = toLocation(router, to);\n\n function prefetchIt() {\n setLoading(true);\n viewPromiseRef.current = resolve(router, location)\n .then((v) => {\n setView(v);\n return v;\n })\n .catch((e) => {\n setError(e);\n throw e;\n })\n .finally(() => setLoading(false));\n }\n\n function handlePrefetch() {\n if (viewPromiseRef.current) return;\n prefetchIt();\n }\n\n function handleClick(e: MouseEvent<HTMLAnchorElement>) {\n e.preventDefault();\n if (!viewPromiseRef.current) {\n prefetchIt();\n }\n commit(router, viewPromiseRef.current!, location).finally(\n () => (viewPromiseRef.current = undefined)\n );\n }\n\n useEffect(() => {\n viewPromiseRef.current = undefined;\n }, [to, router]);\n\n const linkContext = useMemo(\n () => ({loading, error, view}),\n [loading, error, view]\n );\n\n return (\n <Context.Provider value={linkContext}>\n <a\n {...rest}\n href={createHref(router, to)}\n onMouseEnter={handlePrefetch}\n onClick={handleClick}\n >\n {children}\n </a>\n </Context.Provider>\n );\n}\n"],"names":["Link","to","rest","router","useRouter","lock","useRef","href","createHref","onClick","e","preventDefault","current","navigate","finally","Context","createContext","loading","usePrefetch","useContext","PrefetchLink","children","viewPromiseRef","setLoading","useState","error","setError","view","setView","location","toLocation","prefetchIt","resolve","then","v","catch","useEffect","undefined","linkContext","useMemo","_jsx","Provider","value","onMouseEnter","commit"],"mappings":"ugBAUA,SAAwBA,GAAKC,GAACA,KAAOC,IACnC,MAAMC,EAASC,IACTC,EAAOC,GAAO;;AASpB,SAEE,IAAA,IAAOJ,EAAMK,KAAMC,EAAWL,EAAQF,GAAKQ,QAT7C,SAAqBC,GACnBA,EAAEC,iBAEEN,EAAKO,UACTP,EAAKO,SAAU,EACfC,EAASV,EAAQF,GAAIa,QAAQ,IAAOT,EAAKO,SAAU,GACrD,GAKF,CCTA,MAAMG,iBAAUC,EAAmC,CAACC,SAAS,IAMtD,SAASC,IACd,OAAOC,EAAWJ,EACpB,CAOA,SAAwBK,GAAanB,GAACA,EAAAA,SAAIoB,KAAanB,IACrD,MAAMC,EAASC,IACTkB,EAAiBhB,WAChBW,EAASM,GAAcC,GAAS,IAChCC,EAAOC,GAAYF,KACnBG,EAAMC,GAAWJ,IAClBK,EAAWC,EAAW3B,EAAQF,GAEpC,SAAS8B,IACPR,GAAW,GACXD,EAAeV,QAAUoB,EAAQ7B,EAAQ0B,GACtCI,KAAMC,IACLN,EAAQM,GACDA,IAERC,MAAOzB,IAEN,MADAgB,EAAShB,GACHA,IAEPI,QAAQ,IAAMS,GAAW,GAC9B,CAiBAa,EAAU,KACRd,EAAeV,aAAUyB,GACxB,CAACpC,EAAIE,IAER,MAAMmC,EAAcC,EAClB,KAAA,CAAQtB,UAASQ,QAAOE,SACxB,CAACV,EAASQ,EAAOE;AAGnB,OACEa,EAACzB,EAAQ0B,SAAQ,CAACC,MAAOJ,EAAYjB,0BACnC,IAAA,IACMnB,EACJK,KAAMC,EAAWL,EAAQF,GACzB0C,aA7BN,WACMrB,EAAeV,SACnBmB,GACF,EA2BMtB,QAzBN,SAAqBC,GACnBA,EAAEC,iBACGW,EAAeV,SAClBmB,IAEFa,EAAOzC,EAAQmB,EAAeV,QAAUiB,GAAUf,QAChD,IAAOQ,EAAeV,eAE1B,EAiB2BS,cAM7B"}
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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"}
package/dist/server.cjs CHANGED
@@ -1,2 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-CsvbhYZu.cjs");exports.resolveServerView=e.resolveServerView;
2
- //# sourceMappingURL=server.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ssr-BVFDJcr7.cjs");exports.resolveServerView=e.resolveServerView;
package/dist/server.js CHANGED
@@ -1,2 +1 @@
1
- export{r as resolveServerView}from"./ssr-CiHFLO3B.js";
2
- //# sourceMappingURL=server.js.map
1
+ import{n as r}from"./ssr-C17nRDBM.js";export{r as resolveServerView};
@@ -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 c(){return(0,e.useContext)(i)}function s(){return c()}var l=(0,e.createContext)([void 0,{}]);function d(){return(0,e.useContext)(l)}function f(){return d()[1]}function p({children:r,name:n,data:o}){const u=f(),i=(0,e.useMemo)(()=>[o,n?{...u,[n]:o}:u],[o,n,u]);return(0,t.jsx)(l.Provider,{value:i,children:r})}var x=(0,e.createContext)(void 0);function h(){return(0,e.useContext)(x)}function m(e){const[r,t]=d();return e?t[e]:r}var y=(0,e.createContext)(void 0);function b(){return(0,e.useContext)(y)}function v(e,r){return P(e,r,(e,r)=>e?.(r))}var j=new WeakMap;function g(e,r){const t=new Array(e.length);return P(e,r,(e,r)=>Promise.resolve(e?.(r)).then(e=>t[r.index]=e)).then(e=>(j.set(e,t),e))}function P(e,{router:r,location:o},u){return Promise.all(e.map(({route:i},a)=>{const c={matched:e,params:(0,n.mergeMatchedParams)(e,a),index:a,router:r,location:o,search:(0,n.parseSearchInput)(o.search)};return Promise.all([i.search?(0,n.parseSearch)(i.search,o.search).then(e=>(c.search=e,u(i.data,c))):u(i.data,c),function(){if(!i.component)return s;const e=i.component(c);return Promise.resolve(e).then(e=>"default"in e?e.default:e)}()]).then(([e,r])=>(0,t.jsx)(p,{data:e,name:i.name,children:(0,t.jsx)(x.Provider,{value:c,children:(0,t.jsx)(r,{})})}),e=>{if(!i.errorComponent)throw e;return(0,t.jsx)(p,{data:void 0,name:i.name,children:(0,t.jsx)(x.Provider,{value:c,children:(0,t.jsx)(i.errorComponent,{error:e,ctx:c})})})})})).then(e=>e.reverse().reduce((e,r)=>(0,t.jsx)(a,{value:e,children:r})))}var w=(0,e.createContext)(null);function C({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]),s=(0,e.useCallback)(()=>i.current,[]),l=(0,e.useCallback)(()=>(0,n.getCurrentView)(r),[r]),d=(0,u.useSyncExternalStore)(c,s,l);return(0,t.jsx)(w.Provider,{value:r,children:void 0===o?d:(0,t.jsx)(a,{value:d,children:o})})}function O(e,r,{resolveView:t=v,...o}={}){return(0,n.create)(e,r,t,o)}function R({routes:r,children:u,...i},a){const[c,s]=(0,o.splitProps)(i,["baseUrl","currentView"]),{baseUrl:l,currentView:d}=c,f=(0,e.useMemo)(()=>O(r,a(),c),[r,a,l,d]),[p,x]=(0,e.useState)();(0,e.useEffect)(()=>{(0,n.setOptions)(f,{...s,onLoadingChange(e){x(e&&{key:(0,o.uniqId)(),status:e})}})},[f,s]);const h=(0,e.useMemo)(()=>(0,t.jsx)(C,{router:f,children:u}),[f,u]);return(0,t.jsx)(y.Provider,{value:p,children:h})}function M(e){return R(e,r.createBrowserHistory)}function S(e){return R(e,r.createHashHistory)}function H({initialEntries:t,initialIndex:n,...o}){return R(o,(0,e.useMemo)(()=>()=>(0,r.createMemoryHistory)({initialEntries:t,initialIndex:n}),[t,n]))}function V(){const r=(0,e.useContext)(w);if(!r)throw new Error("useRouter() must be used within a <Router> component");return r}var q="_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)(C,{router:e,children:n}),(0,t.jsx)("script",{...o?.scriptAttributes,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`window.${o?.hydrateKey||q} = ${a={data:u,location:r,index:i},JSON.stringify(a).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")};`}})]});var a})}function k(e,t,{scriptAttributes:u,hydrateKey:i,...a}={}){const c=(0,n.create)(e,(0,r.createMemoryHistory)({initialEntries:[t]}),g,a);return E(c,(0,o.isString)(t)?(0,n.toLocation)(c,t):t,{scriptAttributes:u,hydrateKey:i})}function I(e,t){const{data:o,location:u,index:i=0}=window[t?.hydrateKey||q],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)=>P(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 S}}),Object.defineProperty(exports,"HistoryRouter",{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,"MemoryRouter",{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,"Router",{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,"View",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"createRouter",{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,"hydrate",{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,"resolveServerView",{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,"resolveView",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"useData",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"useLoading",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"useMatched",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"useNamedData",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"useRouter",{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,"useView",{enumerable:!0,get:function(){return c}});
2
+ //# sourceMappingURL=ssr-BVFDJcr7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr-BVFDJcr7.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 * Used for route component to render child route component.\n * It just render the return of {@link useView}\n * @group Components\n */\nexport function View() {\n return useView();\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}: 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 };\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, ViewProvider} from '@@/context';\nimport {create, getCurrentView, listen, setOptions} 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\n return (\n <RouterContext.Provider value={router}>\n {children === undefined ? (\n view\n ) : (\n <ViewProvider value={view}>{children}</ViewProvider>\n )}\n </RouterContext.Provider>\n );\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 router = useMemo(\n () => createRouter(routes, createHistory(), tracked),\n [routes, createHistory, baseUrl, currentView]\n );\n\n const [loading, setLoading] = useState<LoadStatus>();\n // Options are applied in an effect(never during render) and refreshed on\n // every commit, so `onLoadingChange` always sees 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,8 @@
1
+ import{createContext as r,useCallback as e,useContext as n,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 m,getCurrentView as v,listen as p,mergeMatchedParams as y,parseSearch as w,parseSearchInput as x,resolve as g,setOptions as P,toLocation as b}from"@native-router/core";import{isString as R,splitProps as A,uniqId as E}from"@native-router/core/util";import{useSyncExternalStore as K}from"use-sync-external-store/shim";var S=r(null);function C(r){/* @__PURE__ */
2
+ return h(S.Provider,{...r})}function I(){return n(S)}function V(){return I()}var _=r([void 0,{}]);function k(){return n(_)}function H(){return k()[1]}function L({children:r,name:e,data:n}){const t=H(),o=i(()=>[n,e?{...t,[e]:n}:t],[n,e,t]);/* @__PURE__ */
3
+ return h(_.Provider,{value:o,children:r})}var M=r(void 0);function U(){return n(M)}function W(r){const[e,n]=k();return r?n[r]:e}var $=r(void 0);function j(){return n($)}function D(r,e){return O(r,e,(r,e)=>r?.(e))}var J=/* @__PURE__ */new WeakMap;function N(r,e){const n=new Array(r.length);return O(r,e,(r,e)=>Promise.resolve(r?.(e)).then(r=>n[e.index]=r)).then(r=>(J.set(r,n),r))}function O(r,{router:e,location:n},t){return Promise.all(r.map(({route:i},o)=>{const a={matched:r,params:y(r,o),index:o,router:e,location:n,search:x(n.search)};return Promise.all([i.search?w(i.search,n.search).then(r=>(a.search=r,t(i.data,a))):t(i.data,a),function(){if(!i.component)return V;const r=i.component(a);return Promise.resolve(r).then(r=>"default"in r?r.default:r)}()]).then(([r,e])=>/* @__PURE__ */h(L,{data:r,name:i.name,children:/* @__PURE__ */h(M.Provider,{value:a,children:/* @__PURE__ */h(e,{})})}),r=>{if(!i.errorComponent)throw r;/* @__PURE__ */
4
+ return h(L,{data:void 0,name:i.name,children:/* @__PURE__ */h(M.Provider,{value:a,children:/* @__PURE__ */h(i.errorComponent,{error:r,ctx:a})})})})})).then(r=>r.reverse().reduce((r,e)=>/* @__PURE__ */h(C,{value:r,children:e})))}var T=r(null);function q({router:r,children:n}){const t=o(v(r)),i=e(e=>p(r,r=>{t.current=r,e()}),[r]),a=e(()=>t.current,[]),u=e(()=>v(r),[r]),c=K(i,a,u);/* @__PURE__ */
5
+ return h(T.Provider,{value:r,children:void 0===n?c:/* @__PURE__ */h(C,{value:c,children:n})})}function z(r,e,{resolveView:n=D,...t}={}){return m(r,e,n,t)}function B({routes:r,children:e,...n},o){const[u,c]=A(n,["baseUrl","currentView"]),{baseUrl:s,currentView:l}=u,d=i(()=>z(r,o(),u),[r,o,s,l]),[f,m]=a();t(()=>{P(d,{...c,onLoadingChange(r){m(r&&{key:E(),status:r})}})},[d,c]);const v=i(()=>/* @__PURE__ */h(q,{router:d,children:e}),[d,e]);/* @__PURE__ */
6
+ return h($.Provider,{value:f,children:v})}function F(r){return B(r,u)}function G(r){return B(r,c)}function Q({initialEntries:r,initialIndex:e,...n}){return B(n,i(()=>()=>s({initialEntries:r,initialIndex:e}),[r,e]))}function X(){const r=n(T);if(!r)throw new Error("useRouter() must be used within a <Router> component");return r}var Y="_nativeRouterReactSSRData";function Z(r,e,n){return g(r,e).then(t=>{const i=function(r){return J.get(r)}(t),o=r.history.location.state?.index||0;/* @__PURE__ */
7
+ return f(d,{children:[/* @__PURE__ */h(q,{router:r,children:t}),/* @__PURE__ */h("script",{...n?.scriptAttributes,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`window.${n?.hydrateKey||Y} = ${a={data:i,location:e,index:o},JSON.stringify(a).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")};`}})]});var a})}function rr(r,e,{scriptAttributes:n,hydrateKey:t,...i}={}){const o=m(r,s({initialEntries:[e]}),N,i);return Z(o,R(e)?b(o,e):e,{scriptAttributes:n,hydrateKey:t})}function er(r,e){const{data:n,location:t,index:i=0}=window[e?.hydrateKey||Y],o=u();o.replace(l(o.location),{index:i});const a=m(r,o,function(r){return(e,n)=>O(e,n,(e,n)=>r[n.index])}(n),e);return g(a,t).then(r=>({view:r,router:a}))}export{Q as a,X as c,W as d,j as f,I as h,F as i,D as l,H as m,rr as n,q as o,U as p,G as r,z as s,er as t,V as u};
8
+ //# sourceMappingURL=ssr-C17nRDBM.js.map