navgo 6.0.2 → 6.0.3
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/changelog.md +6 -0
- package/index.d.ts +18 -1
- package/index.js +41 -5
- package/llms.txt +3 -2
- package/package.json +1 -1
- package/readme.md +14 -5
package/changelog.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
## v6
|
|
4
4
|
|
|
5
5
|
- add route groups for nested layouts/shared loaders; expose ordered `matches` (layouts → route) on `nav.to.matches` and `router.route`
|
|
6
|
+
- add optional route group `id`s plus keyed `layouts` lookups on navigation targets, `match()`, and `router.route` for direct access to shared layout/group data
|
|
7
|
+
- migration:
|
|
8
|
+
- before: `const app_data = nav.to?.matches?.find(m => m.type === 'layout')?.data`
|
|
9
|
+
- after: `const app_data = nav.to?.layouts?.app?.data`
|
|
10
|
+
- docs/typed surfaces now describe `match()` as returning `{ route, params, matches, layouts }` and `window.navgo.route` as including `layouts`
|
|
11
|
+
- covered on direct `match()`, completed navigations, `popstate`, and preload-reused navigations
|
|
6
12
|
- run group loaders (and group `before_route_leave`) for matched child routes; preload caches the full loader chain and `goto` reuses it
|
|
7
13
|
- use `param_rules` for per-param validation + coercion (superseding `param_validators`, which has been removed)
|
|
8
14
|
- example:
|
package/index.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ export interface LoaderContext {
|
|
|
83
83
|
export interface Match<T = unknown> {
|
|
84
84
|
/** Matched layout/group wrapper or the final route tuple. */
|
|
85
85
|
type: 'layout' | 'route'
|
|
86
|
+
/** Present when `type === 'layout'` and the route group declared an `id`. */
|
|
87
|
+
id?: string
|
|
86
88
|
/** Present when `type === 'layout'`. */
|
|
87
89
|
layout?: any
|
|
88
90
|
/** Present when `type === 'route'`. */
|
|
@@ -91,7 +93,17 @@ export interface Match<T = unknown> {
|
|
|
91
93
|
data?: unknown
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
export interface LayoutMatch<T = unknown> extends Match<T> {
|
|
97
|
+
type: 'layout'
|
|
98
|
+
id: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Keyed lookup into matched layout/group wrappers. Values are the same objects as in `matches`. */
|
|
102
|
+
export type LayoutsMap<T = unknown> = Record<string, LayoutMatch<T>>
|
|
103
|
+
|
|
94
104
|
export interface RouteGroup<T = unknown> {
|
|
105
|
+
/** Optional stable key for direct access via `layouts[id]`. Must be unique across groups. */
|
|
106
|
+
id?: string
|
|
95
107
|
/** Optional layout component/module (router does not render; it just forwards this). */
|
|
96
108
|
layout?: any
|
|
97
109
|
/** Load data for this layout group. Return a LoadPlan (object) or a Promise for arbitrary data. */
|
|
@@ -109,6 +121,7 @@ export type RouteEntry<T = unknown> = RouteTuple<T> | RouteGroup<T>
|
|
|
109
121
|
|
|
110
122
|
export interface PreloadBundle<T = unknown> {
|
|
111
123
|
matches: Match<T>[]
|
|
124
|
+
layouts?: LayoutsMap<T>
|
|
112
125
|
data?: unknown
|
|
113
126
|
}
|
|
114
127
|
|
|
@@ -133,6 +146,8 @@ export interface NavigationTarget<T = unknown> {
|
|
|
133
146
|
route: RouteTuple<T> | null
|
|
134
147
|
/** Ordered matches for nested layouts and the final route (outer → inner). */
|
|
135
148
|
matches?: Match<T>[]
|
|
149
|
+
/** Keyed lookup into matched layout/group wrappers by `RouteGroup.id`. */
|
|
150
|
+
layouts?: LayoutsMap<T>
|
|
136
151
|
/** Optional data from route loader when available. */
|
|
137
152
|
data?: unknown
|
|
138
153
|
}
|
|
@@ -156,6 +171,7 @@ export interface MatchResult<T = unknown> {
|
|
|
156
171
|
route: RouteTuple<T>
|
|
157
172
|
params: Params
|
|
158
173
|
matches: Match<T>[]
|
|
174
|
+
layouts: LayoutsMap<T>
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
// For convenience in docs/types, alias the class instance type
|
|
@@ -217,12 +233,13 @@ export default class Navgo<T = unknown> {
|
|
|
217
233
|
init(): Promise<void>
|
|
218
234
|
/** Remove listeners installed by `init()`. */
|
|
219
235
|
destroy(): void
|
|
220
|
-
/** Writable store with current { url, route, params, matches, search_params }. */
|
|
236
|
+
/** Writable store with current { url, route, params, matches, layouts, search_params }. */
|
|
221
237
|
readonly route: import('svelte/store').Writable<{
|
|
222
238
|
url: URL
|
|
223
239
|
route: RouteTuple<T> | null
|
|
224
240
|
params: Params
|
|
225
241
|
matches: Match<T>[]
|
|
242
|
+
layouts: LayoutsMap<T>
|
|
226
243
|
search_params: Record<string, unknown>
|
|
227
244
|
}>
|
|
228
245
|
/** Last completed navigation object. */
|
package/index.js
CHANGED
|
@@ -49,8 +49,15 @@ export default class Navgo {
|
|
|
49
49
|
#base_rgx = /^\/+/
|
|
50
50
|
/** @type {Map<string, { promise?: Promise<PreloadBundle>, data?: PreloadBundle }>} */
|
|
51
51
|
#preloads = new Map()
|
|
52
|
-
/** @type {{ url: URL|null, route: RouteTuple|null, params: Params, matches: Match[], search_params: Record<string, unknown> }} */
|
|
53
|
-
#current = {
|
|
52
|
+
/** @type {{ url: URL|null, route: RouteTuple|null, params: Params, matches: Match[], layouts: Record<string, Match>, search_params: Record<string, unknown> }} */
|
|
53
|
+
#current = {
|
|
54
|
+
url: null,
|
|
55
|
+
route: null,
|
|
56
|
+
params: {},
|
|
57
|
+
matches: [],
|
|
58
|
+
layouts: Object.create(null),
|
|
59
|
+
search_params: {},
|
|
60
|
+
}
|
|
54
61
|
/** @type {number} */
|
|
55
62
|
#route_idx = 0
|
|
56
63
|
/** @type {boolean} */
|
|
@@ -81,6 +88,7 @@ export default class Navgo {
|
|
|
81
88
|
route: null,
|
|
82
89
|
params: {},
|
|
83
90
|
matches: [],
|
|
91
|
+
layouts: Object.create(null),
|
|
84
92
|
search_params: {},
|
|
85
93
|
})
|
|
86
94
|
/** @type {Navigation|null} */
|
|
@@ -632,10 +640,17 @@ export default class Navgo {
|
|
|
632
640
|
}
|
|
633
641
|
}
|
|
634
642
|
|
|
643
|
+
#build_layouts(matches) {
|
|
644
|
+
const out = Object.create(null)
|
|
645
|
+
for (const m of matches || []) if (m.type === 'layout' && m.id) out[m.id] = m
|
|
646
|
+
return out
|
|
647
|
+
}
|
|
648
|
+
|
|
635
649
|
#build_matches(route, stack) {
|
|
636
650
|
const out = []
|
|
637
651
|
for (const g of stack || []) {
|
|
638
652
|
const obj = { type: 'layout', layout: g.layout }
|
|
653
|
+
if (g.id) obj.id = g.id
|
|
639
654
|
Object.defineProperty(obj, '__entry', { value: g })
|
|
640
655
|
out.push(obj)
|
|
641
656
|
}
|
|
@@ -671,6 +686,7 @@ export default class Navgo {
|
|
|
671
686
|
for (let i = 0; i < matches.length; i++) matches[i].data = datas[i]
|
|
672
687
|
return {
|
|
673
688
|
matches,
|
|
689
|
+
layouts: this.#build_layouts(matches),
|
|
674
690
|
data: datas[datas.length - 1],
|
|
675
691
|
search: { schema, opts, defaults, search_params },
|
|
676
692
|
}
|
|
@@ -689,6 +705,7 @@ export default class Navgo {
|
|
|
689
705
|
params: this.#current.params || {},
|
|
690
706
|
route: this.#current.route,
|
|
691
707
|
matches: this.#current.matches || [],
|
|
708
|
+
layouts: this.#current.layouts || Object.create(null),
|
|
692
709
|
}
|
|
693
710
|
: null
|
|
694
711
|
return {
|
|
@@ -746,7 +763,7 @@ export default class Navgo {
|
|
|
746
763
|
|
|
747
764
|
let nav = this.#make_nav({
|
|
748
765
|
type: nav_type,
|
|
749
|
-
to: { url, params: {}, route: null, matches: [] },
|
|
766
|
+
to: { url, params: {}, route: null, matches: [], layouts: Object.create(null) },
|
|
750
767
|
event: ev_param,
|
|
751
768
|
})
|
|
752
769
|
ℹ('[🧭 goto]', 'start', {
|
|
@@ -780,8 +797,9 @@ export default class Navgo {
|
|
|
780
797
|
params: hit.params || {},
|
|
781
798
|
route: hit.route || null,
|
|
782
799
|
matches: hit.matches || [],
|
|
800
|
+
layouts: hit.layouts || this.#build_layouts(hit.matches || []),
|
|
783
801
|
}
|
|
784
|
-
: { url, params: {}, route: null, matches: [] }
|
|
802
|
+
: { url, params: {}, route: null, matches: [], layouts: Object.create(null) }
|
|
785
803
|
if (match_error) nav.to.data = { __error: match_error }
|
|
786
804
|
|
|
787
805
|
// before_navigate (skip initial)
|
|
@@ -856,6 +874,10 @@ export default class Navgo {
|
|
|
856
874
|
route: match_error ? null : hit?.route || null,
|
|
857
875
|
params: match_error ? {} : hit?.params || {},
|
|
858
876
|
matches,
|
|
877
|
+
layouts:
|
|
878
|
+
hit && !match_error
|
|
879
|
+
? bundle?.layouts || this.#build_layouts(matches)
|
|
880
|
+
: Object.create(null),
|
|
859
881
|
search_params: {},
|
|
860
882
|
}
|
|
861
883
|
|
|
@@ -870,6 +892,7 @@ export default class Navgo {
|
|
|
870
892
|
params: prev.params || {},
|
|
871
893
|
route: prev.route,
|
|
872
894
|
matches: prev.matches || [],
|
|
895
|
+
layouts: prev.layouts || Object.create(null),
|
|
873
896
|
}
|
|
874
897
|
: null,
|
|
875
898
|
to: {
|
|
@@ -877,6 +900,7 @@ export default class Navgo {
|
|
|
877
900
|
params: match_error ? {} : hit?.params || {},
|
|
878
901
|
route: match_error ? null : hit?.route || null,
|
|
879
902
|
matches,
|
|
903
|
+
layouts: this.#current.layouts || Object.create(null),
|
|
880
904
|
data,
|
|
881
905
|
},
|
|
882
906
|
event: ev_param,
|
|
@@ -1147,10 +1171,12 @@ export default class Navgo {
|
|
|
1147
1171
|
}
|
|
1148
1172
|
|
|
1149
1173
|
ℹ('[🧭 match]', 'hit', { pattern: obj.data?.[0], params })
|
|
1174
|
+
const matches = this.#build_matches(obj.data, obj.stack)
|
|
1150
1175
|
return {
|
|
1151
1176
|
route: obj.data || null,
|
|
1152
1177
|
params,
|
|
1153
|
-
matches
|
|
1178
|
+
matches,
|
|
1179
|
+
layouts: this.#build_layouts(matches),
|
|
1154
1180
|
}
|
|
1155
1181
|
}
|
|
1156
1182
|
ℹ('[🧭 match]', 'miss', { url: url_raw })
|
|
@@ -1166,6 +1192,8 @@ export default class Navgo {
|
|
|
1166
1192
|
this.#base_rgx =
|
|
1167
1193
|
this.#base == '/' ? /^\/+/ : new RegExp('^\\' + this.#base + '(?=\\/|$)\\/?', 'i')
|
|
1168
1194
|
|
|
1195
|
+
const group_ids = new Map()
|
|
1196
|
+
|
|
1169
1197
|
function compile_routes(entries, stack = []) {
|
|
1170
1198
|
const out = []
|
|
1171
1199
|
for (const e of entries || []) {
|
|
@@ -1181,6 +1209,14 @@ export default class Navgo {
|
|
|
1181
1209
|
continue
|
|
1182
1210
|
}
|
|
1183
1211
|
if (e && typeof e === 'object' && Array.isArray(e.routes)) {
|
|
1212
|
+
if (e.id != null) {
|
|
1213
|
+
if (typeof e.id !== 'string' || !e.id) {
|
|
1214
|
+
throw new Error('Route group id must be a non-empty string')
|
|
1215
|
+
}
|
|
1216
|
+
if (group_ids.has(e.id))
|
|
1217
|
+
throw new Error(`Duplicate route group id "${e.id}"`)
|
|
1218
|
+
group_ids.set(e.id, e)
|
|
1219
|
+
}
|
|
1184
1220
|
out.push(...compile_routes(e.routes, stack.concat(e)))
|
|
1185
1221
|
continue
|
|
1186
1222
|
}
|
package/llms.txt
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- `goto()` and `preload()` are safe to call without `await` from events (no unhandled rejections); errors surface via `nav.to.data.__error` (or preload bundle `data.__error`)
|
|
7
7
|
- `router.init()` attaches the router instance to `window.navgo` by default
|
|
8
8
|
- stores:
|
|
9
|
-
- `window.navgo.route`: `{ url, route, params, matches, search_params }`
|
|
9
|
+
- `window.navgo.route`: `{ url, route, params, matches, layouts, search_params }`
|
|
10
10
|
- `window.navgo.is_navigating`: boolean
|
|
11
11
|
|
|
12
12
|
## Setup (App Wiring)
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
- Handle 404 / loader errors:
|
|
53
53
|
- `const err = nav.to?.data?.__error; if (err?.status === 404) show_404 = true`
|
|
54
54
|
- Shared layout data:
|
|
55
|
-
-
|
|
55
|
+
- use `nav.to.layouts?.id?.data` or `$route.layouts?.id?.data` for direct access to matched group data
|
|
56
|
+
- `nav.to.matches` remains the ordered outer → inner structure; layouts are `m.type === 'layout'`, route leaf is `m.type === 'route'`
|
|
56
57
|
- Stable scroll panes:
|
|
57
58
|
- set `id="pane"` or `data-scroll-id="pane"` on scroll containers to get popstate restoration
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -114,6 +114,7 @@ Each route group is an object:
|
|
|
114
114
|
|
|
115
115
|
```js
|
|
116
116
|
{
|
|
117
|
+
id?: string,
|
|
117
118
|
layout?: any,
|
|
118
119
|
loader?: (ctx) => LoadPlan | Promise<unknown>,
|
|
119
120
|
before_route_leave?: (nav) => void,
|
|
@@ -121,6 +122,7 @@ Each route group is an object:
|
|
|
121
122
|
}
|
|
122
123
|
```
|
|
123
124
|
|
|
125
|
+
- `id` enables direct keyed access via `nav.to.layouts[id]` / `$route.layouts[id]`. IDs must be unique across route groups.
|
|
124
126
|
- `layout` is forwarded into `nav.to.matches` (the router does not render anything).
|
|
125
127
|
- `loader` runs for every matched child route in the group.
|
|
126
128
|
- `before_route_leave` runs when leaving a matched route within the group.
|
|
@@ -171,7 +173,7 @@ Important: Navgo only processes routes that match your `base` path.
|
|
|
171
173
|
|
|
172
174
|
### Instance stores
|
|
173
175
|
|
|
174
|
-
- `router.route` -- `Writable<{ url: URL; route: RouteTuple|null; params: Params; matches: Match[]; search_params: Record<string, unknown> }>`
|
|
176
|
+
- `router.route` -- `Writable<{ url: URL; route: RouteTuple|null; params: Params; matches: Match[]; layouts: Record<string, Match>; search_params: Record<string, unknown> }>`
|
|
175
177
|
- Readonly property that holds the current snapshot.
|
|
176
178
|
- Subscribe to react to changes; Navgo updates it on every URL change.
|
|
177
179
|
- `router.is_navigating` -- `Writable<boolean>`
|
|
@@ -315,8 +317,8 @@ The `Navigation` object contains:
|
|
|
315
317
|
```ts
|
|
316
318
|
{
|
|
317
319
|
type: 'link' | 'goto' | 'popstate' | 'leave',
|
|
318
|
-
from: { url, params, route, matches } | null,
|
|
319
|
-
to: { url, params, route, matches, data } | null,
|
|
320
|
+
from: { url, params, route, matches, layouts } | null,
|
|
321
|
+
to: { url, params, route, matches, layouts, data } | null,
|
|
320
322
|
will_unload: boolean,
|
|
321
323
|
cancelled: boolean,
|
|
322
324
|
event?: Event,
|
|
@@ -324,7 +326,7 @@ The `Navigation` object contains:
|
|
|
324
326
|
}
|
|
325
327
|
```
|
|
326
328
|
|
|
327
|
-
`nav.to.matches` is ordered **outer → inner** and
|
|
329
|
+
`nav.to.matches` is ordered **outer → inner** and remains the canonical structure:
|
|
328
330
|
|
|
329
331
|
```js
|
|
330
332
|
for (const m of nav.to?.matches || []) {
|
|
@@ -333,6 +335,13 @@ for (const m of nav.to?.matches || []) {
|
|
|
333
335
|
}
|
|
334
336
|
```
|
|
335
337
|
|
|
338
|
+
If a matched route group declares an `id`, Navgo also exposes a keyed lookup that points at the same match object:
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
const session = nav.to?.layouts?.app?.data
|
|
342
|
+
const admin_layout = $route.layouts?.admin
|
|
343
|
+
```
|
|
344
|
+
|
|
336
345
|
#### Order & cancellation:
|
|
337
346
|
|
|
338
347
|
- Router calls `before_route_leave` on the current route (leave).
|
|
@@ -556,7 +565,7 @@ scroll flow
|
|
|
556
565
|
### Method-by-Method Semantics
|
|
557
566
|
|
|
558
567
|
- `format(uri)` -- normalizes a path relative to `base`. Returns `false` when `uri` is outside of `base`.
|
|
559
|
-
- `match(uri)` -- returns a Promise of `{ route, params } | null` using string/RegExp patterns and `param_rules` (Valibot schemas). Awaits an async `validate(params)` if provided.
|
|
568
|
+
- `match(uri)` -- returns a Promise of `{ route, params, matches, layouts } | null` using string/RegExp patterns and `param_rules` (Valibot schemas). Awaits an async `validate(params)` if provided.
|
|
560
569
|
- `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`.
|
|
561
570
|
- `init()` -- wires global listeners (`popstate`, `pushstate`, `replacestate`, click) and optional hover/tap preloading; immediately processes the current location.
|
|
562
571
|
- `destroy()` -- removes listeners added by `init()`.
|