navgo 6.0.2 → 6.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/changelog.md +6 -0
- package/index.d.ts +24 -3
- package/index.js +58 -6
- package/llms.txt +3 -2
- package/package.json +1 -1
- package/readme.md +17 -6
- package/utils.js +29 -2
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
|
@@ -7,6 +7,10 @@ export * as v from 'valibot'
|
|
|
7
7
|
*/
|
|
8
8
|
export type RawParam = string | null | undefined
|
|
9
9
|
export type Params = Record<string, any>
|
|
10
|
+
export type SearchParams = Record<string, unknown>
|
|
11
|
+
export type SearchParamsStore = import('svelte/store').Writable<SearchParams> & {
|
|
12
|
+
toString(): string
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
export type ParamSchema = import('valibot').BaseSchema<unknown, unknown, any>
|
|
12
16
|
export type ParamRule = ParamSchema | { schema?: ParamSchema; coercer?: (value: RawParam) => any }
|
|
@@ -83,6 +87,8 @@ export interface LoaderContext {
|
|
|
83
87
|
export interface Match<T = unknown> {
|
|
84
88
|
/** Matched layout/group wrapper or the final route tuple. */
|
|
85
89
|
type: 'layout' | 'route'
|
|
90
|
+
/** Present when `type === 'layout'` and the route group declared an `id`. */
|
|
91
|
+
id?: string
|
|
86
92
|
/** Present when `type === 'layout'`. */
|
|
87
93
|
layout?: any
|
|
88
94
|
/** Present when `type === 'route'`. */
|
|
@@ -91,7 +97,17 @@ export interface Match<T = unknown> {
|
|
|
91
97
|
data?: unknown
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
export interface LayoutMatch<T = unknown> extends Match<T> {
|
|
101
|
+
type: 'layout'
|
|
102
|
+
id: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Keyed lookup into matched layout/group wrappers. Values are the same objects as in `matches`. */
|
|
106
|
+
export type LayoutsMap<T = unknown> = Record<string, LayoutMatch<T>>
|
|
107
|
+
|
|
94
108
|
export interface RouteGroup<T = unknown> {
|
|
109
|
+
/** Optional stable key for direct access via `layouts[id]`. Must be unique across groups. */
|
|
110
|
+
id?: string
|
|
95
111
|
/** Optional layout component/module (router does not render; it just forwards this). */
|
|
96
112
|
layout?: any
|
|
97
113
|
/** Load data for this layout group. Return a LoadPlan (object) or a Promise for arbitrary data. */
|
|
@@ -109,6 +125,7 @@ export type RouteEntry<T = unknown> = RouteTuple<T> | RouteGroup<T>
|
|
|
109
125
|
|
|
110
126
|
export interface PreloadBundle<T = unknown> {
|
|
111
127
|
matches: Match<T>[]
|
|
128
|
+
layouts?: LayoutsMap<T>
|
|
112
129
|
data?: unknown
|
|
113
130
|
}
|
|
114
131
|
|
|
@@ -133,6 +150,8 @@ export interface NavigationTarget<T = unknown> {
|
|
|
133
150
|
route: RouteTuple<T> | null
|
|
134
151
|
/** Ordered matches for nested layouts and the final route (outer → inner). */
|
|
135
152
|
matches?: Match<T>[]
|
|
153
|
+
/** Keyed lookup into matched layout/group wrappers by `RouteGroup.id`. */
|
|
154
|
+
layouts?: LayoutsMap<T>
|
|
136
155
|
/** Optional data from route loader when available. */
|
|
137
156
|
data?: unknown
|
|
138
157
|
}
|
|
@@ -156,6 +175,7 @@ export interface MatchResult<T = unknown> {
|
|
|
156
175
|
route: RouteTuple<T>
|
|
157
176
|
params: Params
|
|
158
177
|
matches: Match<T>[]
|
|
178
|
+
layouts: LayoutsMap<T>
|
|
159
179
|
}
|
|
160
180
|
|
|
161
181
|
// For convenience in docs/types, alias the class instance type
|
|
@@ -217,20 +237,21 @@ export default class Navgo<T = unknown> {
|
|
|
217
237
|
init(): Promise<void>
|
|
218
238
|
/** Remove listeners installed by `init()`. */
|
|
219
239
|
destroy(): void
|
|
220
|
-
/** Writable store with current { url, route, params, matches, search_params }. */
|
|
240
|
+
/** Writable store with current { url, route, params, matches, layouts, search_params }. */
|
|
221
241
|
readonly route: import('svelte/store').Writable<{
|
|
222
242
|
url: URL
|
|
223
243
|
route: RouteTuple<T> | null
|
|
224
244
|
params: Params
|
|
225
245
|
matches: Match<T>[]
|
|
226
|
-
|
|
246
|
+
layouts: LayoutsMap<T>
|
|
247
|
+
search_params: SearchParams
|
|
227
248
|
}>
|
|
228
249
|
/** Last completed navigation object. */
|
|
229
250
|
nav: Navigation | null
|
|
230
251
|
/** Writable store indicating active navigation. */
|
|
231
252
|
readonly is_navigating: import('svelte/store').Writable<boolean>
|
|
232
253
|
/** Writable store of validated search params for the current route. */
|
|
233
|
-
readonly search_params:
|
|
254
|
+
readonly search_params: SearchParamsStore
|
|
234
255
|
/** Invalidate cache entries by canonical keys (URLs) or tags. */
|
|
235
256
|
invalidate(keys_or_tags: string | string[]): Promise<void>
|
|
236
257
|
}
|
package/index.js
CHANGED
|
@@ -3,7 +3,9 @@ import { debounce } from 'es-toolkit/function'
|
|
|
3
3
|
import { isPromise } from 'es-toolkit/predicate'
|
|
4
4
|
import * as v from 'valibot'
|
|
5
5
|
import {
|
|
6
|
+
build_search_string,
|
|
6
7
|
build_search_url,
|
|
8
|
+
create_search_store,
|
|
7
9
|
merge_search_opts,
|
|
8
10
|
normalize_path,
|
|
9
11
|
read_search,
|
|
@@ -49,8 +51,15 @@ export default class Navgo {
|
|
|
49
51
|
#base_rgx = /^\/+/
|
|
50
52
|
/** @type {Map<string, { promise?: Promise<PreloadBundle>, data?: PreloadBundle }>} */
|
|
51
53
|
#preloads = new Map()
|
|
52
|
-
/** @type {{ url: URL|null, route: RouteTuple|null, params: Params, matches: Match[], search_params: Record<string, unknown> }} */
|
|
53
|
-
#current = {
|
|
54
|
+
/** @type {{ url: URL|null, route: RouteTuple|null, params: Params, matches: Match[], layouts: Record<string, Match>, search_params: Record<string, unknown> }} */
|
|
55
|
+
#current = {
|
|
56
|
+
url: null,
|
|
57
|
+
route: null,
|
|
58
|
+
params: {},
|
|
59
|
+
matches: [],
|
|
60
|
+
layouts: Object.create(null),
|
|
61
|
+
search_params: {},
|
|
62
|
+
}
|
|
54
63
|
/** @type {number} */
|
|
55
64
|
#route_idx = 0
|
|
56
65
|
/** @type {boolean} */
|
|
@@ -81,12 +90,13 @@ export default class Navgo {
|
|
|
81
90
|
route: null,
|
|
82
91
|
params: {},
|
|
83
92
|
matches: [],
|
|
93
|
+
layouts: Object.create(null),
|
|
84
94
|
search_params: {},
|
|
85
95
|
})
|
|
86
96
|
/** @type {Navigation|null} */
|
|
87
97
|
nav = null
|
|
88
98
|
is_navigating = writable(false)
|
|
89
|
-
search_params =
|
|
99
|
+
search_params = create_search_store()
|
|
90
100
|
|
|
91
101
|
//
|
|
92
102
|
// Event listeners
|
|
@@ -414,9 +424,23 @@ export default class Navgo {
|
|
|
414
424
|
this.#search_opts.debounce > 0
|
|
415
425
|
? debounce(v => this.#commit_search(v), this.#search_opts.debounce)
|
|
416
426
|
: null
|
|
427
|
+
this.search_params.set_stringifier(v => this.#stringify_search(v))
|
|
417
428
|
this.#set_search_store(search?.search_params ?? {})
|
|
418
429
|
}
|
|
419
430
|
|
|
431
|
+
#stringify_search(values) {
|
|
432
|
+
if (!this.#search_schema) return ''
|
|
433
|
+
const cur = this.#current.url || new URL(location.href)
|
|
434
|
+
return build_search_string(
|
|
435
|
+
cur,
|
|
436
|
+
values,
|
|
437
|
+
this.#search_keys,
|
|
438
|
+
this.#search_defaults,
|
|
439
|
+
this.#search_opts,
|
|
440
|
+
isEqual,
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
420
444
|
/* Read + validate search params from URL. */
|
|
421
445
|
#sync_search_from_url(url) {
|
|
422
446
|
if (!this.#search_schema) return this.#set_search_store({})
|
|
@@ -632,10 +656,17 @@ export default class Navgo {
|
|
|
632
656
|
}
|
|
633
657
|
}
|
|
634
658
|
|
|
659
|
+
#build_layouts(matches) {
|
|
660
|
+
const out = Object.create(null)
|
|
661
|
+
for (const m of matches || []) if (m.type === 'layout' && m.id) out[m.id] = m
|
|
662
|
+
return out
|
|
663
|
+
}
|
|
664
|
+
|
|
635
665
|
#build_matches(route, stack) {
|
|
636
666
|
const out = []
|
|
637
667
|
for (const g of stack || []) {
|
|
638
668
|
const obj = { type: 'layout', layout: g.layout }
|
|
669
|
+
if (g.id) obj.id = g.id
|
|
639
670
|
Object.defineProperty(obj, '__entry', { value: g })
|
|
640
671
|
out.push(obj)
|
|
641
672
|
}
|
|
@@ -671,6 +702,7 @@ export default class Navgo {
|
|
|
671
702
|
for (let i = 0; i < matches.length; i++) matches[i].data = datas[i]
|
|
672
703
|
return {
|
|
673
704
|
matches,
|
|
705
|
+
layouts: this.#build_layouts(matches),
|
|
674
706
|
data: datas[datas.length - 1],
|
|
675
707
|
search: { schema, opts, defaults, search_params },
|
|
676
708
|
}
|
|
@@ -689,6 +721,7 @@ export default class Navgo {
|
|
|
689
721
|
params: this.#current.params || {},
|
|
690
722
|
route: this.#current.route,
|
|
691
723
|
matches: this.#current.matches || [],
|
|
724
|
+
layouts: this.#current.layouts || Object.create(null),
|
|
692
725
|
}
|
|
693
726
|
: null
|
|
694
727
|
return {
|
|
@@ -746,7 +779,7 @@ export default class Navgo {
|
|
|
746
779
|
|
|
747
780
|
let nav = this.#make_nav({
|
|
748
781
|
type: nav_type,
|
|
749
|
-
to: { url, params: {}, route: null, matches: [] },
|
|
782
|
+
to: { url, params: {}, route: null, matches: [], layouts: Object.create(null) },
|
|
750
783
|
event: ev_param,
|
|
751
784
|
})
|
|
752
785
|
ℹ('[🧭 goto]', 'start', {
|
|
@@ -780,8 +813,9 @@ export default class Navgo {
|
|
|
780
813
|
params: hit.params || {},
|
|
781
814
|
route: hit.route || null,
|
|
782
815
|
matches: hit.matches || [],
|
|
816
|
+
layouts: hit.layouts || this.#build_layouts(hit.matches || []),
|
|
783
817
|
}
|
|
784
|
-
: { url, params: {}, route: null, matches: [] }
|
|
818
|
+
: { url, params: {}, route: null, matches: [], layouts: Object.create(null) }
|
|
785
819
|
if (match_error) nav.to.data = { __error: match_error }
|
|
786
820
|
|
|
787
821
|
// before_navigate (skip initial)
|
|
@@ -856,6 +890,10 @@ export default class Navgo {
|
|
|
856
890
|
route: match_error ? null : hit?.route || null,
|
|
857
891
|
params: match_error ? {} : hit?.params || {},
|
|
858
892
|
matches,
|
|
893
|
+
layouts:
|
|
894
|
+
hit && !match_error
|
|
895
|
+
? bundle?.layouts || this.#build_layouts(matches)
|
|
896
|
+
: Object.create(null),
|
|
859
897
|
search_params: {},
|
|
860
898
|
}
|
|
861
899
|
|
|
@@ -870,6 +908,7 @@ export default class Navgo {
|
|
|
870
908
|
params: prev.params || {},
|
|
871
909
|
route: prev.route,
|
|
872
910
|
matches: prev.matches || [],
|
|
911
|
+
layouts: prev.layouts || Object.create(null),
|
|
873
912
|
}
|
|
874
913
|
: null,
|
|
875
914
|
to: {
|
|
@@ -877,6 +916,7 @@ export default class Navgo {
|
|
|
877
916
|
params: match_error ? {} : hit?.params || {},
|
|
878
917
|
route: match_error ? null : hit?.route || null,
|
|
879
918
|
matches,
|
|
919
|
+
layouts: this.#current.layouts || Object.create(null),
|
|
880
920
|
data,
|
|
881
921
|
},
|
|
882
922
|
event: ev_param,
|
|
@@ -1147,10 +1187,12 @@ export default class Navgo {
|
|
|
1147
1187
|
}
|
|
1148
1188
|
|
|
1149
1189
|
ℹ('[🧭 match]', 'hit', { pattern: obj.data?.[0], params })
|
|
1190
|
+
const matches = this.#build_matches(obj.data, obj.stack)
|
|
1150
1191
|
return {
|
|
1151
1192
|
route: obj.data || null,
|
|
1152
1193
|
params,
|
|
1153
|
-
matches
|
|
1194
|
+
matches,
|
|
1195
|
+
layouts: this.#build_layouts(matches),
|
|
1154
1196
|
}
|
|
1155
1197
|
}
|
|
1156
1198
|
ℹ('[🧭 match]', 'miss', { url: url_raw })
|
|
@@ -1166,6 +1208,8 @@ export default class Navgo {
|
|
|
1166
1208
|
this.#base_rgx =
|
|
1167
1209
|
this.#base == '/' ? /^\/+/ : new RegExp('^\\' + this.#base + '(?=\\/|$)\\/?', 'i')
|
|
1168
1210
|
|
|
1211
|
+
const group_ids = new Map()
|
|
1212
|
+
|
|
1169
1213
|
function compile_routes(entries, stack = []) {
|
|
1170
1214
|
const out = []
|
|
1171
1215
|
for (const e of entries || []) {
|
|
@@ -1181,6 +1225,14 @@ export default class Navgo {
|
|
|
1181
1225
|
continue
|
|
1182
1226
|
}
|
|
1183
1227
|
if (e && typeof e === 'object' && Array.isArray(e.routes)) {
|
|
1228
|
+
if (e.id != null) {
|
|
1229
|
+
if (typeof e.id !== 'string' || !e.id) {
|
|
1230
|
+
throw new Error('Route group id must be a non-empty string')
|
|
1231
|
+
}
|
|
1232
|
+
if (group_ids.has(e.id))
|
|
1233
|
+
throw new Error(`Duplicate route group id "${e.id}"`)
|
|
1234
|
+
group_ids.set(e.id, e)
|
|
1235
|
+
}
|
|
1184
1236
|
out.push(...compile_routes(e.routes, stack.concat(e)))
|
|
1185
1237
|
continue
|
|
1186
1238
|
}
|
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,15 +173,16 @@ 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[];
|
|
176
|
+
- `router.route` -- `Writable<{ url: URL; route: RouteTuple|null; params: Params; matches: Match[]; layouts: Record<string, Match>; search_params: SearchParams }>`
|
|
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>`
|
|
178
180
|
- `true` while a navigation is in flight (between start and completion/cancel).
|
|
179
|
-
- `router.search_params` -- `Writable<
|
|
181
|
+
- `router.search_params` -- `Writable<SearchParams> & { toString(): string }`
|
|
180
182
|
- Writable store of validated search params for the **current** route.
|
|
181
183
|
- If the current route defines a `search_schema`, this store is kept in sync with the URL.
|
|
182
184
|
- Writing to it updates the URL search string (optionally debounced).
|
|
185
|
+
- The store object has a custom `toString()` that returns the canonical query string (without the leading `?`) for the current route, using `URLSearchParams` encoding.
|
|
183
186
|
|
|
184
187
|
Example:
|
|
185
188
|
|
|
@@ -255,6 +258,7 @@ Notes:
|
|
|
255
258
|
|
|
256
259
|
- Writes are **shallow** (URL changes via `replace_state` / `push_state`), so loaders are not re-run automatically.
|
|
257
260
|
- If you want a full navigation, call `router.goto(...)` with a new URL.
|
|
261
|
+
- You can serialize the current managed query with `router.search_params.toString()`.
|
|
258
262
|
|
|
259
263
|
### Route Hooks
|
|
260
264
|
|
|
@@ -315,8 +319,8 @@ The `Navigation` object contains:
|
|
|
315
319
|
```ts
|
|
316
320
|
{
|
|
317
321
|
type: 'link' | 'goto' | 'popstate' | 'leave',
|
|
318
|
-
from: { url, params, route, matches } | null,
|
|
319
|
-
to: { url, params, route, matches, data } | null,
|
|
322
|
+
from: { url, params, route, matches, layouts } | null,
|
|
323
|
+
to: { url, params, route, matches, layouts, data } | null,
|
|
320
324
|
will_unload: boolean,
|
|
321
325
|
cancelled: boolean,
|
|
322
326
|
event?: Event,
|
|
@@ -324,7 +328,7 @@ The `Navigation` object contains:
|
|
|
324
328
|
}
|
|
325
329
|
```
|
|
326
330
|
|
|
327
|
-
`nav.to.matches` is ordered **outer → inner** and
|
|
331
|
+
`nav.to.matches` is ordered **outer → inner** and remains the canonical structure:
|
|
328
332
|
|
|
329
333
|
```js
|
|
330
334
|
for (const m of nav.to?.matches || []) {
|
|
@@ -333,6 +337,13 @@ for (const m of nav.to?.matches || []) {
|
|
|
333
337
|
}
|
|
334
338
|
```
|
|
335
339
|
|
|
340
|
+
If a matched route group declares an `id`, Navgo also exposes a keyed lookup that points at the same match object:
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
const session = nav.to?.layouts?.app?.data
|
|
344
|
+
const admin_layout = $route.layouts?.admin
|
|
345
|
+
```
|
|
346
|
+
|
|
336
347
|
#### Order & cancellation:
|
|
337
348
|
|
|
338
349
|
- Router calls `before_route_leave` on the current route (leave).
|
|
@@ -556,7 +567,7 @@ scroll flow
|
|
|
556
567
|
### Method-by-Method Semantics
|
|
557
568
|
|
|
558
569
|
- `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.
|
|
570
|
+
- `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
571
|
- `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
572
|
- `init()` -- wires global listeners (`popstate`, `pushstate`, `replacestate`, click) and optional hover/tap preloading; immediately processes the current location.
|
|
562
573
|
- `destroy()` -- removes listeners added by `init()`.
|
package/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { writable } from 'svelte/store'
|
|
1
2
|
import * as v from 'valibot'
|
|
2
3
|
|
|
3
4
|
export function normalize_path(value) {
|
|
@@ -125,7 +126,7 @@ function safe_json(value) {
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
export function
|
|
129
|
+
export function build_search_string(cur, values, keys, defaults, opts, same_value) {
|
|
129
130
|
let sp = new URLSearchParams(cur.search)
|
|
130
131
|
const as = opts?.array_style
|
|
131
132
|
for (const k of keys) {
|
|
@@ -159,8 +160,34 @@ export function build_search_url(cur, values, keys, defaults, opts, same_value)
|
|
|
159
160
|
if (opts?.sort) {
|
|
160
161
|
sp = new URLSearchParams([...sp.entries()].sort(([a], [b]) => a.localeCompare(b)))
|
|
161
162
|
}
|
|
163
|
+
return sp.toString()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function build_search_url(cur, values, keys, defaults, opts, same_value) {
|
|
162
167
|
const next = new URL(cur.href)
|
|
163
|
-
const s =
|
|
168
|
+
const s = build_search_string(cur, values, keys, defaults, opts, same_value)
|
|
164
169
|
next.search = s ? `?${s}` : ''
|
|
165
170
|
return next.href === cur.href ? null : next
|
|
166
171
|
}
|
|
172
|
+
|
|
173
|
+
export function create_search_store(stringify = () => '') {
|
|
174
|
+
let current = {}
|
|
175
|
+
const store = writable(current)
|
|
176
|
+
const api = {
|
|
177
|
+
subscribe: store.subscribe,
|
|
178
|
+
set(value) {
|
|
179
|
+
current = value && typeof value === 'object' ? value : {}
|
|
180
|
+
store.set(current)
|
|
181
|
+
},
|
|
182
|
+
update(fn) {
|
|
183
|
+
api.set(fn(current))
|
|
184
|
+
},
|
|
185
|
+
toString() {
|
|
186
|
+
return stringify(current)
|
|
187
|
+
},
|
|
188
|
+
set_stringifier(fn) {
|
|
189
|
+
stringify = fn || (() => '')
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
return api
|
|
193
|
+
}
|