navgo 3.0.3 → 3.0.4
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/index.d.ts +23 -23
- package/index.js +10 -10
- package/package.json +27 -18
- package/readme.md +76 -76
package/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface ValidatorHelpers {
|
|
|
14
14
|
oneOf(values: Iterable<string>): (value: string | null | undefined) => boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** Optional per-route hooks recognized by
|
|
17
|
+
/** Optional per-route hooks recognized by Navgo. */
|
|
18
18
|
export interface Hooks {
|
|
19
19
|
/** Validate params with custom per-param validators. Return `false` to skip a match. */
|
|
20
20
|
param_validators?: Record<string, (value: string | null | undefined) => boolean>
|
|
@@ -55,27 +55,11 @@ export interface MatchResult<T = unknown> {
|
|
|
55
55
|
params: Params
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
format(url: string): string | false
|
|
61
|
-
/** SvelteKit-like navigation that runs loaders before updating the URL. */
|
|
62
|
-
goto(url: string, opts?: { replace?: boolean }): Promise<void>
|
|
63
|
-
/** Shallow push — updates URL/state without triggering handlers. */
|
|
64
|
-
pushState(url?: string | URL, state?: any): void
|
|
65
|
-
/** Shallow replace — updates URL/state without triggering handlers. */
|
|
66
|
-
replaceState(url?: string | URL, state?: any): void
|
|
67
|
-
/** Manually preload loaders for a URL (deduped). */
|
|
68
|
-
preload(url: string): Promise<unknown | void>
|
|
69
|
-
/** Try to match `url`; returns route tuple and params or `null`. Supports async `validate`. */
|
|
70
|
-
match(url: string): Promise<MatchResult<T> | null>
|
|
71
|
-
/** Attach history + click listeners and immediately process current location. */
|
|
72
|
-
init(): Promise<void>
|
|
73
|
-
/** Remove listeners installed by `init()`. */
|
|
74
|
-
destroy(): void
|
|
75
|
-
}
|
|
58
|
+
// For convenience in docs/types, alias the class instance type
|
|
59
|
+
export type Router<T = unknown> = Navgo<T>
|
|
76
60
|
|
|
77
|
-
/** Router metadata stored under `history.state.
|
|
78
|
-
export interface
|
|
61
|
+
/** Router metadata stored under `history.state.__navgo`. */
|
|
62
|
+
export interface NavgoHistoryMeta {
|
|
79
63
|
/** Monotonic index of the current history entry for scroll restoration. */
|
|
80
64
|
idx: number
|
|
81
65
|
/** Present when the entry was created via shallow `pushState`/`replaceState`. */
|
|
@@ -102,9 +86,25 @@ export interface Options {
|
|
|
102
86
|
url_changed?(payload: any): void
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
/**
|
|
106
|
-
export default class Navgo<T = unknown>
|
|
89
|
+
/** Navgo default export: class-based router. */
|
|
90
|
+
export default class Navgo<T = unknown> {
|
|
107
91
|
constructor(routes?: Array<RouteTuple<T>>, opts?: Options)
|
|
92
|
+
/** Format `url` relative to the configured base. */
|
|
93
|
+
format(url: string): string | false
|
|
94
|
+
/** SvelteKit-like navigation that runs loaders before updating the URL. */
|
|
95
|
+
goto(url: string, opts?: { replace?: boolean }): Promise<void>
|
|
96
|
+
/** Shallow push — updates URL/state without triggering handlers. */
|
|
97
|
+
pushState(url?: string | URL, state?: any): void
|
|
98
|
+
/** Shallow replace — updates URL/state without triggering handlers. */
|
|
99
|
+
replaceState(url?: string | URL, state?: any): void
|
|
100
|
+
/** Manually preload loaders for a URL (deduped). */
|
|
101
|
+
preload(url: string): Promise<unknown | void>
|
|
102
|
+
/** Try to match `url`; returns route tuple and params or `null`. Supports async `validate`. */
|
|
103
|
+
match(url: string): Promise<MatchResult<T> | null>
|
|
104
|
+
/** Attach history + click listeners and immediately process current location. */
|
|
105
|
+
init(): Promise<void>
|
|
106
|
+
/** Remove listeners installed by `init()`. */
|
|
107
|
+
destroy(): void
|
|
108
108
|
/** Built-in validator helpers (namespaced). */
|
|
109
109
|
static validators: ValidatorHelpers
|
|
110
110
|
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse } from 'regexparam'
|
|
2
2
|
|
|
3
|
-
const ℹ = (...args) =>
|
|
3
|
+
const ℹ = (...args) => {}
|
|
4
4
|
|
|
5
5
|
export default class Navgo {
|
|
6
6
|
#opts = {
|
|
@@ -67,7 +67,7 @@ export default class Navgo {
|
|
|
67
67
|
// ignore popstate while a hash-originating nav is in flight
|
|
68
68
|
if (this.#hash_navigating) return
|
|
69
69
|
|
|
70
|
-
const st = ev?.state?.
|
|
70
|
+
const st = ev?.state?.__navgo
|
|
71
71
|
ℹ('[🧭 event:popstate]', st)
|
|
72
72
|
// Hash-only or state-only change: pathname+search unchanged -> skip loaders
|
|
73
73
|
const cur = this.#current.url
|
|
@@ -97,7 +97,7 @@ export default class Navgo {
|
|
|
97
97
|
this.#hash_navigating = false
|
|
98
98
|
const prev = history.state && typeof history.state == 'object' ? history.state : {}
|
|
99
99
|
const next_idx = this.#route_idx + 1
|
|
100
|
-
const next_state = { ...prev,
|
|
100
|
+
const next_state = { ...prev, __navgo: { ...prev.__navgo, idx: next_idx } }
|
|
101
101
|
history.replaceState(next_state, '', location.href)
|
|
102
102
|
this.#route_idx = next_idx
|
|
103
103
|
ℹ('[🧭 event:hashchange]', { idx: next_idx, href: location.href })
|
|
@@ -125,7 +125,7 @@ export default class Navgo {
|
|
|
125
125
|
// persist scroll for refresh / session restore
|
|
126
126
|
try {
|
|
127
127
|
sessionStorage.setItem(
|
|
128
|
-
`
|
|
128
|
+
`__navgo_scroll:${location.href}`,
|
|
129
129
|
JSON.stringify({ x: scrollX, y: scrollY }),
|
|
130
130
|
)
|
|
131
131
|
} catch {}
|
|
@@ -259,7 +259,7 @@ export default class Navgo {
|
|
|
259
259
|
if (nav.cancelled) {
|
|
260
260
|
// use history.go to cancel the nav, and jump back to where we are
|
|
261
261
|
if (is_popstate) {
|
|
262
|
-
const new_idx = ev_param?.state?.
|
|
262
|
+
const new_idx = ev_param?.state?.__navgo?.idx
|
|
263
263
|
if (new_idx != null) {
|
|
264
264
|
const delta = new_idx - this.#route_idx
|
|
265
265
|
if (delta) {
|
|
@@ -315,7 +315,7 @@ export default class Navgo {
|
|
|
315
315
|
history.state && typeof history.state == 'object' ? history.state : {}
|
|
316
316
|
const next_state = {
|
|
317
317
|
...prev_state,
|
|
318
|
-
|
|
318
|
+
__navgo: { ...prev_state.__navgo, idx: next_idx, type: nav_type },
|
|
319
319
|
}
|
|
320
320
|
history[(opts.replace ? 'replace' : 'push') + 'State'](next_state, null, url.href)
|
|
321
321
|
ℹ('[🧭 history]', opts.replace ? 'replaceState' : 'pushState', {
|
|
@@ -522,10 +522,10 @@ export default class Navgo {
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
// ensure current history state carries our index
|
|
525
|
-
const cur_idx = history.state?.
|
|
525
|
+
const cur_idx = history.state?.__navgo?.idx
|
|
526
526
|
if (cur_idx == null) {
|
|
527
527
|
const prev = history.state && typeof history.state == 'object' ? history.state : {}
|
|
528
|
-
const next_state = { ...prev,
|
|
528
|
+
const next_state = { ...prev, __navgo: { ...prev.__navgo, idx: this.#route_idx } }
|
|
529
529
|
history.replaceState(next_state, '', location.href)
|
|
530
530
|
ℹ('[🧭 history]', 'init idx', { idx: this.#route_idx })
|
|
531
531
|
} else {
|
|
@@ -567,7 +567,7 @@ export default class Navgo {
|
|
|
567
567
|
const is_initial = ctx && 'from' in ctx ? ctx.from == null : !t
|
|
568
568
|
if (is_initial) {
|
|
569
569
|
try {
|
|
570
|
-
const k = `
|
|
570
|
+
const k = `__navgo_scroll:${location.href}`
|
|
571
571
|
const { x, y } = JSON.parse(sessionStorage.getItem(k))
|
|
572
572
|
sessionStorage.removeItem(k)
|
|
573
573
|
scrollTo(x, y)
|
|
@@ -578,7 +578,7 @@ export default class Navgo {
|
|
|
578
578
|
// 1) On back/forward, restore saved position if available
|
|
579
579
|
if (t === 'popstate') {
|
|
580
580
|
const ev_state = ctx?.state ?? ctx?.event?.state
|
|
581
|
-
const idx = ev_state?.
|
|
581
|
+
const idx = ev_state?.__navgo?.idx
|
|
582
582
|
const target_idx = typeof idx === 'number' ? idx : this.#route_idx - 1
|
|
583
583
|
this.#route_idx = target_idx
|
|
584
584
|
const pos = this.#scroll.get(target_idx)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "navgo",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/mustafa0x/navgo.git"
|
|
@@ -15,11 +15,10 @@
|
|
|
15
15
|
"name": "mustafa j"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"test
|
|
21
|
-
"
|
|
22
|
-
"types": "pnpm --package=typescript@5.6.3 dlx tsc -p test/types"
|
|
18
|
+
"build": "perl -0777 -i -pe 's/console.debug\\(...args\\)/{}/g' index.js",
|
|
19
|
+
"prepublishOnly": "pnpm run build",
|
|
20
|
+
"test": "vitest run index.test.js",
|
|
21
|
+
"types": "tsc -p test/types"
|
|
23
22
|
},
|
|
24
23
|
"files": [
|
|
25
24
|
"*.d.ts",
|
|
@@ -34,26 +33,27 @@
|
|
|
34
33
|
"regexparam": "^3.0.0"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
37
|
-
"@eslint/js": "^9.
|
|
38
|
-
"@playwright/test": "^1.
|
|
36
|
+
"@eslint/js": "^9.37.0",
|
|
37
|
+
"@playwright/test": "^1.56.0",
|
|
39
38
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
40
|
-
"@tailwindcss/vite": "^4.1.
|
|
39
|
+
"@tailwindcss/vite": "^4.1.14",
|
|
41
40
|
"@tsconfig/svelte": "^5.0.5",
|
|
42
|
-
"@types/node": "^24.
|
|
43
|
-
"eslint": "^9.
|
|
41
|
+
"@types/node": "^24.7.2",
|
|
42
|
+
"eslint": "^9.37.0",
|
|
44
43
|
"eslint-plugin-svelte": "^3.12.4",
|
|
45
44
|
"globals": "^16.4.0",
|
|
46
|
-
"jiti": "^2.6.
|
|
45
|
+
"jiti": "^2.6.1",
|
|
47
46
|
"lightningcss": "^1.30.2",
|
|
48
47
|
"prettier": "^3.6.2",
|
|
49
48
|
"prettier-plugin-svelte": "^3.4.0",
|
|
50
49
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
51
|
-
"svelte": "^5.39.
|
|
52
|
-
"tailwindcss": "^4.1.
|
|
50
|
+
"svelte": "^5.39.11",
|
|
51
|
+
"tailwindcss": "^4.1.14",
|
|
53
52
|
"terser": "5.44.0",
|
|
54
|
-
"typescript
|
|
55
|
-
"
|
|
56
|
-
"
|
|
53
|
+
"typescript": "5.9.3",
|
|
54
|
+
"typescript-eslint": "^8.46.0",
|
|
55
|
+
"vite": "^7.1.9",
|
|
56
|
+
"vitest": "^3.2.4"
|
|
57
57
|
},
|
|
58
58
|
"pnpm": {
|
|
59
59
|
"onlyBuiltDependencies": [
|
|
@@ -69,6 +69,15 @@
|
|
|
69
69
|
"quoteProps": "as-needed",
|
|
70
70
|
"bracketSpacing": true,
|
|
71
71
|
"arrowParens": "avoid",
|
|
72
|
-
"useTabs": true
|
|
72
|
+
"useTabs": true,
|
|
73
|
+
"overrides": [
|
|
74
|
+
{
|
|
75
|
+
"files": "*.md",
|
|
76
|
+
"options": {
|
|
77
|
+
"tabWidth": 2,
|
|
78
|
+
"useTabs": false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
73
82
|
}
|
|
74
83
|
}
|
package/readme.md
CHANGED
|
@@ -11,51 +11,51 @@ import Navgo from 'navgo'
|
|
|
11
11
|
|
|
12
12
|
// Define routes up front (strings or RegExp)
|
|
13
13
|
const routes = [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
['/', {}],
|
|
15
|
+
['/users/:username', {}],
|
|
16
|
+
['/books/*', {}],
|
|
17
|
+
[/articles\/(?<year>[0-9]{4})/, {}],
|
|
18
|
+
[/privacy|privacy-policy/, {}],
|
|
19
|
+
[
|
|
20
|
+
'/admin',
|
|
21
|
+
{
|
|
22
|
+
// constrain params with built-ins or your own
|
|
23
|
+
param_validators: {
|
|
24
|
+
/* id: Navgo.validators.int({ min: 1 }) */
|
|
25
|
+
},
|
|
26
|
+
// load data before URL changes; result goes to after_navigate(...)
|
|
27
|
+
loaders: params => fetch('/api/admin').then(r => r.json()),
|
|
28
|
+
// per-route guard; cancel synchronously to block nav
|
|
29
|
+
beforeRouteLeave(nav) {
|
|
30
|
+
if ((nav.type === 'link' || nav.type === 'nav') && !confirm('Enter admin?')) {
|
|
31
|
+
nav.cancel()
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
// Create router with options + callbacks
|
|
39
39
|
const router = new Navgo(routes, {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
base: '/',
|
|
41
|
+
before_navigate(nav) {
|
|
42
|
+
// app-level hook before loaders/URL update; may cancel
|
|
43
|
+
console.log('before_navigate', nav.type, '→', nav.to?.url.pathname)
|
|
44
|
+
},
|
|
45
|
+
after_navigate(nav) {
|
|
46
|
+
// called after routing completes; nav.to.data holds loader result
|
|
47
|
+
if (nav.to?.data?.__error?.status === 404) {
|
|
48
|
+
console.log('404 for', nav.to.url.pathname)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('after_navigate', nav.to?.url.pathname, nav.to?.data)
|
|
53
|
+
},
|
|
54
|
+
url_changed(cur) {
|
|
55
|
+
// fires on shallow/hash/popstate-shallow/404 and full navigations
|
|
56
|
+
// `cur` is the router snapshot: { url: URL, route, params }
|
|
57
|
+
console.log('url_changed', cur.url.href)
|
|
58
|
+
},
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
// Long-lived router: history + <a> bindings
|
|
@@ -92,32 +92,32 @@ Notes:
|
|
|
92
92
|
#### `options`
|
|
93
93
|
|
|
94
94
|
- `base`: `string` (default `'/'`)
|
|
95
|
-
|
|
95
|
+
- App base pathname. With or without leading/trailing slashes is accepted.
|
|
96
96
|
- `before_navigate`: `(nav: Navigation) => void`
|
|
97
|
-
|
|
97
|
+
- App-level hook called once per navigation attempt after the per-route guard and before loaders/URL update. May call `nav.cancel()` synchronously to prevent navigation.
|
|
98
98
|
- `after_navigate`: `(nav: Navigation) => void`
|
|
99
|
-
|
|
99
|
+
- App-level hook called after routing completes (URL updated, data loaded). `nav.to.data` holds any loader data.
|
|
100
100
|
- `url_changed`: `(snapshot: any) => void`
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
- Fires on every URL change — shallow `pushState`/`replaceState`, hash changes, `popstate` shallow entries, 404s, and full navigations.
|
|
102
|
+
- Receives the router's current snapshot: an object like `{ url: URL, route: RouteTuple|null, params: Params }`.
|
|
103
|
+
- The snapshot type is intentionally `any` and may evolve without a breaking change.
|
|
104
104
|
- `preload_delay`: `number` (default `20`)
|
|
105
|
-
|
|
105
|
+
- Delay in ms before hover preloading triggers.
|
|
106
106
|
- `preload_on_hover`: `boolean` (default `true`)
|
|
107
|
-
|
|
107
|
+
- When `false`, disables hover/touch preloading.
|
|
108
108
|
|
|
109
|
-
Important:
|
|
109
|
+
Important: Navgo only processes routes that match your `base` path.
|
|
110
110
|
|
|
111
111
|
### Route Hooks
|
|
112
112
|
|
|
113
113
|
- param_validators?: `Record<string, (value: string|null|undefined) => boolean>`
|
|
114
|
-
|
|
114
|
+
- Validate params (e.g., `id: Navgo.validators.int({ min: 1 })`). Any `false` result skips the route.
|
|
115
115
|
- loaders?(params): `unknown | Promise | Array<unknown|Promise>`
|
|
116
|
-
|
|
116
|
+
- Run before URL changes on `link`/`nav`. Results are cached per formatted path and forwarded to `after_navigate`.
|
|
117
117
|
- validate?(params): `boolean | Promise<boolean>`
|
|
118
|
-
|
|
118
|
+
- Predicate called during matching. If it returns or resolves to `false`, the route is skipped.
|
|
119
119
|
- beforeRouteLeave?(nav): `(nav: Navigation) => void`
|
|
120
|
-
|
|
120
|
+
- Guard called once per navigation attempt on the current route (leave). Call `nav.cancel()` synchronously to prevent navigation. For `popstate`, cancellation auto-reverts the history jump.
|
|
121
121
|
|
|
122
122
|
The `Navigation` object contains:
|
|
123
123
|
|
|
@@ -137,29 +137,29 @@ The `Navigation` object contains:
|
|
|
137
137
|
|
|
138
138
|
- Router calls `before_navigate` on the current route (leave).
|
|
139
139
|
- Call `nav.cancel()` synchronously to cancel.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
- For `link`/`nav`, it stops before URL change.
|
|
141
|
+
- For `popstate`, cancellation causes an automatic `history.go(...)` to revert to the previous index.
|
|
142
|
+
- For `leave`, cancellation triggers the native “Leave site?” dialog (behavior is browser-controlled).
|
|
143
143
|
|
|
144
144
|
Example:
|
|
145
145
|
|
|
146
146
|
```js
|
|
147
147
|
const routes = [
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
148
|
+
[
|
|
149
|
+
'/admin',
|
|
150
|
+
{
|
|
151
|
+
param_validators: {
|
|
152
|
+
/* ... */
|
|
153
|
+
},
|
|
154
|
+
loaders: params => fetch('/api/admin/stats').then(r => r.json()),
|
|
155
|
+
beforeRouteLeave(nav) {
|
|
156
|
+
if (nav.type === 'link' || nav.type === 'nav') {
|
|
157
|
+
if (!confirm('Enter admin area?')) nav.cancel()
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
['/', {}],
|
|
163
163
|
]
|
|
164
164
|
|
|
165
165
|
const router = new Navgo(routes, { base: '/app' })
|
|
@@ -204,7 +204,7 @@ The desired path to navigate. If it begins with `/` and does not match the confi
|
|
|
204
204
|
Type: `Object`
|
|
205
205
|
|
|
206
206
|
- replace: `Boolean` (default `false`)
|
|
207
|
-
|
|
207
|
+
- When `true`, uses `history.replaceState`; otherwise `history.pushState`.
|
|
208
208
|
|
|
209
209
|
### init()
|
|
210
210
|
|
|
@@ -312,7 +312,7 @@ This lets you reflect UI state in the URL while deferring route transitions unti
|
|
|
312
312
|
|
|
313
313
|
### History Index & popstate Cancellation
|
|
314
314
|
|
|
315
|
-
To enable `popstate` cancellation,
|
|
315
|
+
To enable `popstate` cancellation, Navgo stores a monotonic `idx` in `history.state.__navgo.idx`. On `popstate`, a cancelled navigation computes the delta between the target and current `idx` and calls `history.go(-delta)` to return to the prior entry.
|
|
316
316
|
|
|
317
317
|
### Scroll Restoration
|
|
318
318
|
|
|
@@ -320,8 +320,8 @@ Navgo manages scroll manually (sets `history.scrollRestoration = 'manual'`) and
|
|
|
320
320
|
|
|
321
321
|
- Saves the current scroll position for the active history index.
|
|
322
322
|
- On `link`/`nav` (after route commit):
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
- If the URL has a `#hash`, scroll to the matching element `id` or `[name="..."]`.
|
|
324
|
+
- Otherwise, scroll to the top `(0, 0)`.
|
|
325
325
|
- On `popstate`: restore the saved position for the target history index; if not found but there is a `#hash`, scroll to the anchor instead.
|
|
326
326
|
- Shallow `pushState`/`replaceState` never adjust scroll (routing is skipped).
|
|
327
327
|
|