navgo 3.0.8 → 4.0.2
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 +21 -21
- package/index.js +15 -13
- package/package.json +1 -1
- package/readme.md +12 -12
package/index.d.ts
CHANGED
|
@@ -7,23 +7,23 @@ export type Params = Record<string, string | null | undefined>
|
|
|
7
7
|
|
|
8
8
|
/** Built-in validator helpers shape. */
|
|
9
9
|
export interface ValidatorHelpers {
|
|
10
|
-
int(
|
|
10
|
+
int(opts?: {
|
|
11
11
|
min?: number | null
|
|
12
12
|
max?: number | null
|
|
13
|
-
}): (
|
|
14
|
-
one_of(
|
|
13
|
+
}): (value: string | null | undefined) => boolean
|
|
14
|
+
one_of(values: Iterable<string>): (value: string | null | undefined) => boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
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
|
-
param_validators?: Record<string, (
|
|
20
|
+
param_validators?: Record<string, (value: string | null | undefined) => boolean>
|
|
21
21
|
/** Load data for a route before navigation. May return a Promise or an array of values/promises. */
|
|
22
|
-
|
|
22
|
+
loader?(params: Params): unknown | Promise<unknown> | Array<unknown | Promise<unknown>>
|
|
23
23
|
/** Predicate used during match(); may be async. If it returns `false`, the route is skipped. */
|
|
24
|
-
validate?(
|
|
24
|
+
validate?(params: Params): boolean | Promise<boolean>
|
|
25
25
|
/** Route-level navigation guard, called on the current route when leaving it. Synchronous only; call `nav.cancel()` to prevent navigation. */
|
|
26
|
-
before_route_leave?(
|
|
26
|
+
before_route_leave?(nav: Navigation): void
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface NavigationTarget {
|
|
@@ -31,7 +31,7 @@ export interface NavigationTarget {
|
|
|
31
31
|
params: Params
|
|
32
32
|
/** The matched route tuple from your original `routes` list; `null` when unmatched (e.g. external). */
|
|
33
33
|
route: RouteTuple | null
|
|
34
|
-
/** Optional data from route
|
|
34
|
+
/** Optional data from route loader when available. */
|
|
35
35
|
data?: unknown
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -77,10 +77,10 @@ export interface Options {
|
|
|
77
77
|
preload_on_hover?: boolean
|
|
78
78
|
/** Attach instance to window as `window.navgo`. Default true. */
|
|
79
79
|
attach_to_window?: boolean
|
|
80
|
-
/** Global hook fired after per-route `before_route_leave`, before
|
|
81
|
-
before_navigate?(
|
|
80
|
+
/** Global hook fired after per-route `before_route_leave`, before loader/history change. Can cancel. */
|
|
81
|
+
before_navigate?(nav: Navigation): void
|
|
82
82
|
/** Global hook fired after routing completes (data loaded, URL updated, handlers run). */
|
|
83
|
-
after_navigate?(
|
|
83
|
+
after_navigate?(nav: Navigation): void
|
|
84
84
|
/** Optional hook awaited after `after_navigate` and before scroll handling.
|
|
85
85
|
* Useful for UI frameworks (e.g., Svelte) to flush DOM updates so anchor/top
|
|
86
86
|
* scrolling lands on the correct elements.
|
|
@@ -90,24 +90,24 @@ export interface Options {
|
|
|
90
90
|
* Triggers for shallow pushes/replaces, hash changes, popstate-shallow, 404s, and full navigations.
|
|
91
91
|
* Receives the router's current snapshot (eg `{ url: URL, route: RouteTuple|null, params: Params }`).
|
|
92
92
|
*/
|
|
93
|
-
url_changed?(
|
|
93
|
+
url_changed?(payload: any): void
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/** Navgo default export: class-based router. */
|
|
97
97
|
export default class Navgo<T = unknown> {
|
|
98
|
-
constructor(
|
|
98
|
+
constructor(routes?: Array<RouteTuple<T>>, opts?: Options)
|
|
99
99
|
/** Format `url` relative to the configured base. */
|
|
100
|
-
format(
|
|
101
|
-
/** SvelteKit-like navigation that runs
|
|
102
|
-
goto(
|
|
100
|
+
format(url: string): string | false
|
|
101
|
+
/** SvelteKit-like navigation that runs `loader` before updating the URL. */
|
|
102
|
+
goto(url: string, opts?: { replace?: boolean }): Promise<void>
|
|
103
103
|
/** Shallow push — updates URL/state without triggering handlers. */
|
|
104
|
-
push_state(
|
|
104
|
+
push_state(url?: string | URL, state?: any): void
|
|
105
105
|
/** Shallow replace — updates URL/state without triggering handlers. */
|
|
106
|
-
replace_state(
|
|
107
|
-
/** Manually preload
|
|
108
|
-
preload(
|
|
106
|
+
replace_state(url?: string | URL, state?: any): void
|
|
107
|
+
/** Manually preload `loader` for a URL (deduped). */
|
|
108
|
+
preload(url: string): Promise<unknown | void>
|
|
109
109
|
/** Try to match `url`; returns route tuple and params or `null`. Supports async `validate`. */
|
|
110
|
-
match(
|
|
110
|
+
match(url: string): Promise<MatchResult<T> | null>
|
|
111
111
|
/** Attach history + click listeners and immediately process current location. */
|
|
112
112
|
init(): Promise<void>
|
|
113
113
|
/** Remove listeners installed by `init()`. */
|
package/index.js
CHANGED
|
@@ -97,12 +97,12 @@ export default class Navgo {
|
|
|
97
97
|
|
|
98
98
|
const st = ev?.state?.__navgo
|
|
99
99
|
ℹ('[🧭 event:popstate]', st)
|
|
100
|
-
// Hash-only or state-only change: pathname+search unchanged -> skip
|
|
100
|
+
// Hash-only or state-only change: pathname+search unchanged -> skip loader
|
|
101
101
|
const cur = this.#current.url
|
|
102
102
|
const target = new URL(location.href)
|
|
103
103
|
if (cur && target.pathname === cur.pathname) {
|
|
104
104
|
this.#current.url = target
|
|
105
|
-
ℹ(' - [🧭 event:popstate]', 'same path+search; skip
|
|
105
|
+
ℹ(' - [🧭 event:popstate]', 'same path+search; skip loader')
|
|
106
106
|
this.#apply_scroll(ev)
|
|
107
107
|
this.route.set(this.#current)
|
|
108
108
|
return
|
|
@@ -110,7 +110,7 @@ export default class Navgo {
|
|
|
110
110
|
// Explicit shallow entries (pushState/replaceState) regardless of path
|
|
111
111
|
if (st?.shallow) {
|
|
112
112
|
this.#current.url = target
|
|
113
|
-
ℹ(' - [🧭 event:popstate]', 'shallow entry; skip
|
|
113
|
+
ℹ(' - [🧭 event:popstate]', 'shallow entry; skip loader')
|
|
114
114
|
this.#apply_scroll(ev)
|
|
115
115
|
this.route.set(this.#current)
|
|
116
116
|
return
|
|
@@ -254,8 +254,8 @@ export default class Navgo {
|
|
|
254
254
|
return true
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
async #
|
|
258
|
-
const ret_val = route[1].
|
|
257
|
+
async #run_loader(route, params) {
|
|
258
|
+
const ret_val = route[1].loader?.(params)
|
|
259
259
|
return Array.isArray(ret_val) ? Promise.all(ret_val) : ret_val
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -348,18 +348,18 @@ export default class Navgo {
|
|
|
348
348
|
if (nav_id !== this.#nav_active) return
|
|
349
349
|
|
|
350
350
|
//
|
|
351
|
-
//
|
|
351
|
+
// loader
|
|
352
352
|
//
|
|
353
353
|
let data
|
|
354
354
|
if (hit) {
|
|
355
355
|
const pre = this.#preloads.get(path)
|
|
356
356
|
data =
|
|
357
357
|
pre?.data ??
|
|
358
|
-
(await (pre?.promise || this.#
|
|
358
|
+
(await (pre?.promise || this.#run_loader(hit.route, hit.params)).catch(e => ({
|
|
359
359
|
__error: e,
|
|
360
360
|
})))
|
|
361
361
|
this.#preloads.delete(path)
|
|
362
|
-
ℹ('[🧭
|
|
362
|
+
ℹ('[🧭 loader]', pre ? 'using preloaded data' : 'loaded', {
|
|
363
363
|
path,
|
|
364
364
|
preloaded: !!pre,
|
|
365
365
|
has_error: !!data?.__error,
|
|
@@ -409,6 +409,8 @@ export default class Navgo {
|
|
|
409
409
|
},
|
|
410
410
|
event: ev_param,
|
|
411
411
|
})
|
|
412
|
+
this.route.set(this.#current)
|
|
413
|
+
|
|
412
414
|
// await so that apply_scroll is after potential async work
|
|
413
415
|
await this.#opts.after_navigate?.(nav)
|
|
414
416
|
|
|
@@ -424,12 +426,11 @@ export default class Navgo {
|
|
|
424
426
|
await this.#opts.tick?.()
|
|
425
427
|
|
|
426
428
|
this.#apply_scroll(nav)
|
|
427
|
-
this.
|
|
428
|
-
if (nav_id === this.#nav_active) this.is_navigating.set(false)
|
|
429
|
+
this.is_navigating.set(false)
|
|
429
430
|
}
|
|
430
431
|
|
|
431
432
|
/**
|
|
432
|
-
* Shallow push — updates the URL/state but DOES NOT call handlers or
|
|
433
|
+
* Shallow push — updates the URL/state but DOES NOT call handlers or loader.
|
|
433
434
|
*/
|
|
434
435
|
#commit_shallow(url, state, replace) {
|
|
435
436
|
const u = new URL(url || location.href, location.href)
|
|
@@ -476,7 +477,7 @@ export default class Navgo {
|
|
|
476
477
|
}
|
|
477
478
|
|
|
478
479
|
/**
|
|
479
|
-
* Preload
|
|
480
|
+
* Preload loader data for a URL (e.g. to prime cache).
|
|
480
481
|
* Dedupes concurrent preloads for the same path.
|
|
481
482
|
*/
|
|
482
483
|
/** @param {string} url_raw @returns {Promise<unknown|void>} */
|
|
@@ -504,7 +505,7 @@ export default class Navgo {
|
|
|
504
505
|
}
|
|
505
506
|
|
|
506
507
|
const entry = {}
|
|
507
|
-
entry.promise = this.#
|
|
508
|
+
entry.promise = this.#run_loader(hit.route, hit.params).then(data => {
|
|
508
509
|
entry.data = data
|
|
509
510
|
delete entry.promise
|
|
510
511
|
ℹ('[🧭 preload]', 'done', { path })
|
|
@@ -634,6 +635,7 @@ export default class Navgo {
|
|
|
634
635
|
removeEventListener('hashchange', this.#on_hashchange)
|
|
635
636
|
removeEventListener('scroll', this.#scroll_handler, { capture: true })
|
|
636
637
|
this.#areas_pos.clear()
|
|
638
|
+
delete window.navgo
|
|
637
639
|
}
|
|
638
640
|
|
|
639
641
|
#clear_onward_history() {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -24,7 +24,7 @@ const routes = [
|
|
|
24
24
|
/* id: Navgo.validators.int({ min: 1 }) */
|
|
25
25
|
},
|
|
26
26
|
// load data before URL changes; result goes to after_navigate(...)
|
|
27
|
-
|
|
27
|
+
loader: params => fetch('/api/admin').then(r => r.json()),
|
|
28
28
|
// per-route guard; cancel synchronously to block nav
|
|
29
29
|
before_route_leave(nav) {
|
|
30
30
|
if ((nav.type === 'link' || nav.type === 'nav') && !confirm('Enter admin?')) {
|
|
@@ -39,7 +39,7 @@ const routes = [
|
|
|
39
39
|
const router = new Navgo(routes, {
|
|
40
40
|
base: '/',
|
|
41
41
|
before_navigate(nav) {
|
|
42
|
-
// app-level hook before
|
|
42
|
+
// app-level hook before loader/URL update; may cancel
|
|
43
43
|
console.log('before_navigate', nav.type, '→', nav.to?.url.pathname)
|
|
44
44
|
},
|
|
45
45
|
after_navigate(nav) {
|
|
@@ -97,7 +97,7 @@ Notes:
|
|
|
97
97
|
- `base`: `string` (default `'/'`)
|
|
98
98
|
- App base pathname. With or without leading/trailing slashes is accepted.
|
|
99
99
|
- `before_navigate`: `(nav: Navigation) => void`
|
|
100
|
-
- App-level hook called once per navigation attempt after the per-route guard and before
|
|
100
|
+
- App-level hook called once per navigation attempt after the per-route guard and before loader/URL update. May call `nav.cancel()` synchronously to prevent navigation.
|
|
101
101
|
- `after_navigate`: `(nav: Navigation) => void`
|
|
102
102
|
- App-level hook called after routing completes (URL updated, data loaded). `nav.to.data` holds any loader data.
|
|
103
103
|
- `tick`: `() => void | Promise<void>`
|
|
@@ -139,7 +139,7 @@ const {route, is_navigating} = router
|
|
|
139
139
|
|
|
140
140
|
- param_validators?: `Record<string, (value: string|null|undefined) => boolean>`
|
|
141
141
|
- Validate params (e.g., `id: Navgo.validators.int({ min: 1 })`). Any `false` result skips the route.
|
|
142
|
-
-
|
|
142
|
+
- loader?(params): `unknown | Promise | Array<unknown|Promise>`
|
|
143
143
|
- Run before URL changes on `link`/`nav`. Results are cached per formatted path and forwarded to `after_navigate`.
|
|
144
144
|
- validate?(params): `boolean | Promise<boolean>`
|
|
145
145
|
- Predicate called during matching. If it returns or resolves to `false`, the route is skipped.
|
|
@@ -178,7 +178,7 @@ const routes = [
|
|
|
178
178
|
param_validators: {
|
|
179
179
|
/* ... */
|
|
180
180
|
},
|
|
181
|
-
|
|
181
|
+
loader: params => fetch('/api/admin/stats').then(r => r.json()),
|
|
182
182
|
before_route_leave(nav) {
|
|
183
183
|
if (nav.type === 'link' || nav.type === 'nav') {
|
|
184
184
|
if (!confirm('Enter admin area?')) nav.cancel()
|
|
@@ -218,7 +218,7 @@ The path to format.
|
|
|
218
218
|
|
|
219
219
|
Returns: `Promise<void>`
|
|
220
220
|
|
|
221
|
-
Runs any matching route `
|
|
221
|
+
Runs any matching route `loader` before updating the URL and then updates history. Route processing triggers `after_navigate`. Use `replace: true` to replace the current history entry.
|
|
222
222
|
|
|
223
223
|
#### uri
|
|
224
224
|
|
|
@@ -284,8 +284,8 @@ Or with a custom id:
|
|
|
284
284
|
|
|
285
285
|
Returns: `Promise<unknown | void>`
|
|
286
286
|
|
|
287
|
-
Preload a route's `
|
|
288
|
-
Note: Resolves to `undefined` when the matched route has no `
|
|
287
|
+
Preload a route's `loader` data for a given `uri` without navigating. Concurrent calls for the same path are deduped.
|
|
288
|
+
Note: Resolves to `undefined` when the matched route has no `loader`.
|
|
289
289
|
|
|
290
290
|
### push_state(url?, state?)
|
|
291
291
|
|
|
@@ -335,7 +335,7 @@ For `link` and `goto` navigations that match a route:
|
|
|
335
335
|
→ before_route_leave({ type }) // per-route guard
|
|
336
336
|
→ before_navigate(nav) // app-level start
|
|
337
337
|
→ cancelled? yes → stop
|
|
338
|
-
→ no → run
|
|
338
|
+
→ no → run loader(params) // may be value, Promise, or Promise[]
|
|
339
339
|
→ cache data by formatted path
|
|
340
340
|
→ history.push/replaceState(new URL)
|
|
341
341
|
→ after_navigate(nav)
|
|
@@ -344,7 +344,7 @@ For `link` and `goto` navigations that match a route:
|
|
|
344
344
|
```
|
|
345
345
|
|
|
346
346
|
- If a loader throws/rejects, navigation continues and `after_navigate(..., with nav.to.data = { __error })` is delivered so UI can render an error state.
|
|
347
|
-
- For `popstate`,
|
|
347
|
+
- For `popstate`, the route's `loader` runs before completion so content matches the target entry; this improves scroll restoration. Errors are delivered via `after_navigate` with `nav.to.data = { __error }`.
|
|
348
348
|
|
|
349
349
|
### Shallow Routing
|
|
350
350
|
|
|
@@ -384,10 +384,10 @@ scroll flow
|
|
|
384
384
|
|
|
385
385
|
- `format(uri)` -- normalizes a path relative to `base`. Returns `false` when `uri` is outside of `base`.
|
|
386
386
|
- `match(uri)` -- returns a Promise of `{ route, params } | null` using string/RegExp patterns and validators. Awaits an async `validate(params)` if provided.
|
|
387
|
-
- `goto(uri, { replace? })` -- fires route-level `before_route_leave('goto')`, calls global `before_navigate`, saves scroll, runs
|
|
387
|
+
- `goto(uri, { replace? })` -- fires route-level `before_route_leave('goto')`, calls global `before_navigate`, saves scroll, runs loader, pushes/replaces, and completes via `after_navigate`.
|
|
388
388
|
- `init()` -- wires global listeners (`popstate`, `pushstate`, `replacestate`, click) and optional hover/tap preloading; immediately processes the current location.
|
|
389
389
|
- `destroy()` -- removes listeners added by `init()`.
|
|
390
|
-
- `preload(uri)` -- pre-executes a route's `
|
|
390
|
+
- `preload(uri)` -- pre-executes a route's `loader` for a path and caches the result; concurrent calls are deduped.
|
|
391
391
|
- `push_state(url?, state?)` -- shallow push that updates the URL and `history.state` without route processing.
|
|
392
392
|
- `replace_state(url?, state?)` -- shallow replace that updates the URL and `history.state` without route processing.
|
|
393
393
|
|