svelte-navigator-lite 1.1.3 → 1.1.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/README.md CHANGED
@@ -110,32 +110,30 @@ Add `optional: true` to make a trailing segment optional. Optional segments must
110
110
 
111
111
  ## Search Params
112
112
 
113
- Declared in `searchParams`. Listed params are captured into `router.params` if present in the URL, and silently ignored if absent. They never affect whether a route matches.
113
+ Search params are automatically captured into `router.searchParams` when present in the URL. No configuration is needed they never affect whether a route matches.
114
114
 
115
115
  ```ts
116
- 'password-reset': {
117
- rootPath: 'password-reset',
118
- segments: [],
119
- searchParams: ['token'],
120
- }
121
- // /password-reset?token=abc → router.params.token === 'abc'
122
- // /password-reset → router.params === {}
116
+ // /password-reset?token=abc → router.searchParams.token === 'abc'
117
+ // /password-reset → router.searchParams === {}
123
118
  ```
124
119
 
125
120
  ---
126
121
 
127
122
  ## Navigating
128
123
 
129
- ### `router.navigate(route, params?)`
124
+ ### `router.navigate(route, params?, searchParams?)`
130
125
 
131
- Navigate to a named route. Params are used to fill dynamic segments and search params.
126
+ Navigate to a named route. Path params fill dynamic segments; pass search params separately as the third argument.
132
127
 
133
128
  ```ts
134
129
  router.navigate('event', { eventId: '123' });
135
130
  // → /event/123
136
131
 
137
- router.navigate('password-reset', { token: 'abc123' });
132
+ router.navigate('password-reset', undefined, { token: 'abc123' });
138
133
  // → /password-reset?token=abc123
134
+
135
+ router.navigate('event', { eventId: '123' }, { tab: 'details' });
136
+ // → /event/123?tab=details
139
137
  ```
140
138
 
141
139
  Throws if a required param is missing.
@@ -201,7 +199,11 @@ The label of the currently matched route. Falls back to `defaultRoute` if no rou
201
199
 
202
200
  ### `router.params`
203
201
 
204
- An object containing all captured values — path params, required search params, and any present optional search params.
202
+ An object containing the captured path param values for the current route.
203
+
204
+ ### `router.searchParams`
205
+
206
+ An object containing the search params present in the current URL. Empty object when none are present.
205
207
 
206
208
  ### `router.notFound`
207
209
 
@@ -215,7 +217,7 @@ An object containing all captured values — path params, required search params
215
217
  {/if}
216
218
  ```
217
219
 
218
- ### `router.navigate(route, params?)`
220
+ ### `router.navigate(route, params?, searchParams?)`
219
221
 
220
222
  Navigate to a named route, applying guards and building the URL from the route definition.
221
223
 
@@ -11,7 +11,6 @@ export type Route = {
11
11
  enforceVal?: string;
12
12
  optional?: boolean;
13
13
  })[];
14
- searchParams?: string[];
15
14
  routeGuards?: RouteGuard[];
16
15
  };
17
16
  export type RouteGuard = {
@@ -23,12 +22,13 @@ export type Router = ReturnType<typeof _createRouter>;
23
22
  export declare function _createRouter(routeList?: RouteList): {
24
23
  readonly route: string;
25
24
  readonly params: Record<string, string>;
25
+ readonly searchParams: Record<string, string>;
26
26
  readonly notFound: boolean;
27
27
  rootRoute: string;
28
28
  is(route: string): boolean;
29
29
  matches(routes: string[]): boolean;
30
30
  parseUrl: (url: string) => void;
31
- navigate(route: string, params?: Record<string, string>): Promise<void>;
31
+ navigate(route: string, params?: Record<string, string>, searchParams?: Record<string, string>): Promise<void>;
32
32
  registerRoute(name: string, route: Route): void;
33
33
  };
34
34
  export declare let router: Router;
@@ -38,6 +38,7 @@ export function _createRouter(routeList = {}) {
38
38
  let state = $state({
39
39
  current: '',
40
40
  params: {},
41
+ searchParams: {},
41
42
  notFound: false,
42
43
  });
43
44
  let routes = {};
@@ -75,22 +76,17 @@ export function _createRouter(routeList = {}) {
75
76
  params[routeSegment.name] = urlSegment;
76
77
  }
77
78
  }
78
- if (match && route.searchParams) {
79
- for (const key of route.searchParams) {
80
- const val = searchParams.get(key);
81
- if (val)
82
- params[key] = val;
83
- }
84
- }
85
79
  if (match) {
86
80
  state.current = routeName;
87
81
  state.params = params;
82
+ state.searchParams = Object.fromEntries(searchParams.entries());
88
83
  state.notFound = false;
89
84
  return;
90
85
  }
91
86
  }
92
87
  state.current = rootRoute;
93
88
  state.params = {};
89
+ state.searchParams = {};
94
90
  state.notFound = true;
95
91
  }
96
92
  return {
@@ -101,6 +97,9 @@ export function _createRouter(routeList = {}) {
101
97
  get params() {
102
98
  return state.params;
103
99
  },
100
+ get searchParams() {
101
+ return state.searchParams;
102
+ },
104
103
  get notFound() {
105
104
  return state.notFound;
106
105
  },
@@ -114,7 +113,7 @@ export function _createRouter(routeList = {}) {
114
113
  return routes.includes(state.current);
115
114
  },
116
115
  parseUrl,
117
- async navigate(route, params) {
116
+ async navigate(route, params, searchParams) {
118
117
  let path = routes[route].rootPath;
119
118
  for (const guard of routes[route].routeGuards || []) {
120
119
  if (guard.fn()) {
@@ -133,14 +132,8 @@ export function _createRouter(routeList = {}) {
133
132
  path += `/${params[segment.name]}`;
134
133
  }
135
134
  }
136
- if (routes[route].searchParams) {
137
- const searchParams = new URLSearchParams();
138
- for (const param of routes[route].searchParams) {
139
- if (params && params[param]) {
140
- searchParams.set(param, params[param]);
141
- }
142
- }
143
- const qs = searchParams.toString();
135
+ if (searchParams) {
136
+ const qs = new URLSearchParams(searchParams).toString();
144
137
  if (qs)
145
138
  path += `?${qs}`;
146
139
  }
@@ -129,12 +129,11 @@ describe('parseUrl — optional segments', () => {
129
129
  expect(r.notFound).toBe(true);
130
130
  });
131
131
  });
132
- describe('parseUrl — required searchParams', () => {
132
+ describe('parseUrl — search params', () => {
133
133
  const routes = {
134
134
  'password-reset': {
135
135
  rootPath: 'password-reset',
136
136
  segments: [],
137
- searchParams: ['token'],
138
137
  },
139
138
  fallback: { rootPath: 'fallback', segments: [] },
140
139
  };
@@ -142,24 +141,28 @@ describe('parseUrl — required searchParams', () => {
142
141
  const r = makeRouter(routes, 'fallback');
143
142
  r.parseUrl('http://localhost/password-reset?token=abc123');
144
143
  expect(r.route).toBe('password-reset');
145
- expect(r.params).toEqual({ token: 'abc123' });
144
+ expect(r.searchParams).toEqual({ token: 'abc123' });
146
145
  });
147
- it('still matches when a search param is absent', () => {
146
+ it('still matches when no search params are present', () => {
148
147
  const r = makeRouter(routes, 'fallback');
149
148
  r.parseUrl('http://localhost/password-reset');
150
149
  expect(r.route).toBe('password-reset');
151
- expect(r.params).toEqual({});
150
+ expect(r.searchParams).toEqual({});
152
151
  });
153
- it('captures multiple search params independently', () => {
152
+ it('captures multiple search params', () => {
154
153
  const r = makeRouter({
155
- page: {
156
- rootPath: 'items',
157
- segments: [],
158
- searchParams: ['page', 'sort'],
159
- },
160
- }, 'page');
161
- r.parseUrl('http://localhost/items?page=2');
162
- expect(r.params).toEqual({ page: '2' });
154
+ items: { rootPath: 'items', segments: [] },
155
+ }, 'items');
156
+ r.parseUrl('http://localhost/items?page=2&sort=asc');
157
+ expect(r.searchParams).toEqual({ page: '2', sort: 'asc' });
158
+ });
159
+ it('captures search params alongside path params', () => {
160
+ const r = makeRouter({
161
+ event: { rootPath: 'event', segments: [{ name: 'eventId' }] },
162
+ }, 'event');
163
+ r.parseUrl('http://localhost/event/123?tab=details');
164
+ expect(r.params).toEqual({ eventId: '123' });
165
+ expect(r.searchParams).toEqual({ tab: 'details' });
163
166
  });
164
167
  });
165
168
  describe('parseUrl — fallback and notFound', () => {
@@ -240,17 +243,38 @@ describe('navigate()', () => {
240
243
  }, 'event');
241
244
  await expect(r.navigate('event')).rejects.toThrow('Missing parameter eventId');
242
245
  });
243
- it('appends search params to the path', async () => {
246
+ it('appends search params when provided', async () => {
244
247
  const r = makeRouter({
245
- 'password-reset': {
246
- rootPath: 'password-reset',
247
- segments: [],
248
- searchParams: ['token'],
249
- },
248
+ 'password-reset': { rootPath: 'password-reset', segments: [] },
250
249
  }, 'password-reset');
251
- await r.navigate('password-reset', { token: 'abc' });
250
+ await r.navigate('password-reset', undefined, { token: 'abc' });
252
251
  expect(history.pushState).toHaveBeenCalledWith({}, '', '/password-reset?token=abc');
253
252
  });
253
+ it('appends multiple search params', async () => {
254
+ const r = makeRouter({
255
+ items: { rootPath: 'items', segments: [] },
256
+ }, 'items');
257
+ await r.navigate('items', undefined, { page: '2', sort: 'asc' });
258
+ const call = history.pushState.mock.calls[0][2];
259
+ const url = new URL(call, 'http://localhost');
260
+ expect(url.pathname).toBe('/items');
261
+ expect(url.searchParams.get('page')).toBe('2');
262
+ expect(url.searchParams.get('sort')).toBe('asc');
263
+ });
264
+ it('navigates with both path params and search params', async () => {
265
+ const r = makeRouter({
266
+ event: { rootPath: 'event', segments: [{ name: 'eventId' }] },
267
+ }, 'event');
268
+ await r.navigate('event', { eventId: '42' }, { tab: 'details' });
269
+ expect(history.pushState).toHaveBeenCalledWith({}, '', '/event/42?tab=details');
270
+ });
271
+ it('does not append search params when not provided', async () => {
272
+ const r = makeRouter({
273
+ event: { rootPath: 'event', segments: [{ name: 'eventId' }] },
274
+ }, 'event');
275
+ await r.navigate('event', { eventId: '42' });
276
+ expect(history.pushState).toHaveBeenCalledWith({}, '', '/event/42');
277
+ });
254
278
  it('redirects when a guard fn returns true', async () => {
255
279
  const r = makeRouter({
256
280
  login: { rootPath: 'login', segments: [] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-navigator-lite",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "A lightweight router for Svelte 5",
5
5
  "readme": "README.md",
6
6
  "repository": {