diff --git a/.changeset/wicked-brooms-like.md b/.changeset/wicked-brooms-like.md new file mode 100644 index 000000000000..1435015e0fbf --- /dev/null +++ b/.changeset/wicked-brooms-like.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/kit': minor +--- + +Adds optional arguments for URL params `hash` and `searchParams` to `resolve()` from `$app/paths`. +Appends these to the returned URL after resolving the path. diff --git a/packages/kit/src/runtime/app/paths/client.js b/packages/kit/src/runtime/app/paths/client.js index 510f42ede418..a1bebc17681f 100644 --- a/packages/kit/src/runtime/app/paths/client.js +++ b/packages/kit/src/runtime/app/paths/client.js @@ -26,7 +26,9 @@ export function asset(file) { } /** - * Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters. + * Resolve a pathname by prefixing it with the base path, if any, + * or resolve a route ID by populating dynamic segments with route parameters. + * Optionally accepts URL parameters and appends these to the resolved route. * * During server rendering, the base path is relative and depends on the page currently being rendered. * @@ -37,10 +39,26 @@ export function asset(file) { * // using a pathname * const resolved = resolve(`/blog/hello-world`); * - * // using a route ID plus parameters + * // using a route ID plus route parameters * const resolved = resolve('/blog/[slug]', { * slug: 'hello-world' * }); + * + * // using a route ID plus URL parameters as Record + * const resolved = resolve('/blog/search', + * { hash: 'results', searchParams: { author: 'John Doe', year: '2025' } } + * }); + * + * // using a route ID plus URL parameters as URLSearchParams + * const resolved = resolve('/blog/search', + * { hash: 'results', searchParams: new URLSearchParams({ author: 'John Doe', year: '2025' }) } + * }); + * + * // using a route ID plus route parameters and URL parameters + * const resolved = resolve('/blog/[slug]', + * { slug: 'hello-world' }, + * { hash: 'introduction' } + * }); * ``` * @since 2.26 * @@ -49,9 +67,58 @@ export function asset(file) { * @returns {ResolvedPathname} */ export function resolve(...args) { + // args[0] is always the route ID or pathname + const routeID = /** @type {string} */ (args[0]); + + /** @type {Record | undefined} */ + let routeParams; + + /** @type {{ searchParams?: Record | URLSearchParams; hash?: string } | undefined} */ + let urlParams; + + // Determine if args[1] is route params or URL params + if (args.length === 2) { + const searchParamsOrURLParams = args[1]; + // If args[1] is actually undefined, we don't need to do anything + if (searchParamsOrURLParams) { + if ('searchParams' in searchParamsOrURLParams || 'hash' in searchParamsOrURLParams) { + // It's URL params + urlParams = searchParamsOrURLParams; + } else { + // Otherwise, it's route params + routeParams = /** @type {Record} */ (searchParamsOrURLParams); + } + } + } else if (args.length === 3) { + // args[1] is route params, args[2] is URL params + routeParams = args[1]; + urlParams = args[2]; + } + // The type error is correct here, and if someone doesn't pass params when they should there's a runtime error, - // but we don't want to adjust the internal resolve_route function to accept `undefined`, hence the type cast. - return base + resolve_route(args[0], /** @type {Record} */ (args[1])); -} + // but we don't want to adjust the internal resolve_route function to accept undefined, hence the type cast. + let resolvedPath = + base + resolve_route(routeID, /** @type {Record} */ (routeParams)); + + // Append searchParams and hash if provided. These do not affect route resolving. + if (urlParams?.searchParams) { + const { searchParams } = urlParams; + if (searchParams instanceof URLSearchParams) { + resolvedPath += `?${searchParams.toString()}`; + } else { + const query = new URLSearchParams(); + for (const [key, value] of Object.entries(searchParams)) { + query.append(key, value); + } + resolvedPath += `?${query.toString()}`; + } + } + + if (urlParams?.hash) { + resolvedPath += `#${urlParams.hash}`; + } + + return resolvedPath; +} export { base, assets, resolve as resolveRoute }; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index 295201c5fd71..e42b7bbcbed2 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,7 +1,7 @@ -import { Pathname, RouteId, RouteParams } from '$app/types'; +import { Pathname, ResolveURLParams, RouteId, RouteParams } from '$app/types'; export type ResolveArgs = T extends RouteId ? RouteParams extends Record - ? [route: T] - : [route: T, params: RouteParams] - : [route: T]; + ? [route: T, options?: ResolveURLParams] + : [route: T, params: RouteParams, options?: ResolveURLParams] + : [route: T, options?: ResolveURLParams]; diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index dd7c958e6c1a..6f6e3e322e1f 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -111,6 +111,15 @@ declare module '$app/types' { ? ReturnType[T] : Record; + /** + * URL Parameters that can be optionally passed to the `resolve` function. + * Used to specify a hash and/or search parameters to be included in the resolved URL. + */ + export type ResolveURLParams = { + hash?: string; + searchParams?: Record | URLSearchParams; + }; + /** * A utility for getting the parameters associated with a given layout, which is similar to `RouteParams` but also includes optional parameters for any child route. */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 9fde95c8dd4c..d3648a090013 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3043,7 +3043,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { - import type { RouteId, Pathname, ResolvedPathname, RouteParams, Asset } from '$app/types'; + import type { RouteId, Pathname, ResolvedPathname, ResolveURLParams, RouteParams, Asset } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -3070,9 +3070,9 @@ declare module '$app/paths' { ): ResolvedPathname; type ResolveArgs = T extends RouteId ? RouteParams extends Record - ? [route: T] - : [route: T, params: RouteParams] - : [route: T]; + ? [route: T, options?: ResolveURLParams] + : [route: T, params: RouteParams, options?: ResolveURLParams] + : [route: T, options?: ResolveURLParams]; /** * Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path. * @@ -3091,7 +3091,9 @@ declare module '$app/paths' { * */ export function asset(file: Asset): string; /** - * Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters. + * Resolve a pathname by prefixing it with the base path, if any, + * or resolve a route ID by populating dynamic segments with route parameters. + * Optionally accepts URL parameters and appends these to the resolved route. * * During server rendering, the base path is relative and depends on the page currently being rendered. * @@ -3102,10 +3104,26 @@ declare module '$app/paths' { * // using a pathname * const resolved = resolve(`/blog/hello-world`); * - * // using a route ID plus parameters + * // using a route ID plus route parameters * const resolved = resolve('/blog/[slug]', { * slug: 'hello-world' * }); + * + * // using a route ID plus URL parameters as Record + * const resolved = resolve('/blog/search', + * { hash: 'results', searchParams: { author: 'John Doe', year: '2025' } } + * }); + * + * // using a route ID plus URL parameters as URLSearchParams + * const resolved = resolve('/blog/search', + * { hash: 'results', searchParams: new URLSearchParams({ author: 'John Doe', year: '2025' }) } + * }); + * + * // using a route ID plus route parameters and URL parameters + * const resolved = resolve('/blog/[slug]', + * { slug: 'hello-world' }, + * { hash: 'introduction' } + * }); * ``` * @since 2.26 * @@ -3481,6 +3499,15 @@ declare module '$app/types' { ? ReturnType[T] : Record; + /** + * URL Parameters that can be optionally passed to the `resolve` function. + * Used to specify a hash and/or search parameters to be included in the resolved URL. + */ + export type ResolveURLParams = { + hash?: string; + searchParams?: Record | URLSearchParams; + }; + /** * A utility for getting the parameters associated with a given layout, which is similar to `RouteParams` but also includes optional parameters for any child route. */