RouteParams,
RouteLocationNamedRaw,
RouteLocationPathRaw,
+ RouteLocationString,
} from './types'
import { RouterHistory, HistoryState, NavigationType } from './history/common'
import {
routerViewLocationKey,
} from './injectionSymbols'
import { addDevtools } from './devtools'
-import { RouteNamedMap } from './types/named'
+import { RouteNamedMap, RouteStaticPathMap } from './types/named'
/**
* Internal type to define an ErrorHandler
push(
to:
| RouteLocationNamedRaw<RouteNamedMap<Options['routes']>>
- | string
- | RouteLocationPathRaw
+ | RouteLocationString<RouteStaticPathMap<Options['routes']>>
+ | RouteLocationPathRaw<RouteStaticPathMap<Options['routes']>>
): Promise<NavigationFailure | void | undefined>
/**
replace(
to:
| RouteLocationNamedRaw<RouteNamedMap<Options['routes']>>
- | string
- | RouteLocationPathRaw
+ | RouteLocationString<RouteStaticPathMap<Options['routes']>>
+ | RouteLocationPathRaw<RouteStaticPathMap<Options['routes']>>
): Promise<NavigationFailure | void | undefined>
/**
import { RouteRecord, RouteRecordNormalized } from '../matcher/types'
import { HistoryState } from '../history/common'
import { NavigationFailure } from '../errors'
-import { RouteNamedInfo, RouteNamedMapGeneric } from './named'
+import {
+ RouteNamedInfo,
+ RouteNamedMapGeneric,
+ RouteStaticPathMapGeneric,
+} from './named'
export type Lazy<T> = () => Promise<T>
export type Override<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U
/**
* @internal
*/
-export interface LocationAsPath {
- path: string
+export interface LocationAsPath<P extends string = string> {
+ path: P
}
/**
| RouteLocationNamedRaw
/**
- * Route Location that can infer the necessary params based on the name
+ * Route location that can infer full path locations
+ *
+ * @internal
+ */
+export type RouteLocationString<
+ RouteMap extends RouteStaticPathMapGeneric = RouteStaticPathMapGeneric
+> = RouteStaticPathMapGeneric extends RouteMap
+ ? string
+ : {
+ [K in keyof RouteMap]: RouteMap[K]['fullPath']
+ }[keyof RouteMap]
+
+/**
+ * Route Location that can infer the necessary params based on the name.
*
* @internal
*/
RouteLocationOptions
}[Extract<keyof RouteMap, RouteRecordName>]
-export type RouteLocationPathRaw =
- | RouteQueryAndHash & LocationAsPath & RouteLocationOptions
+/**
+ * Route Location that can infer the possible paths.
+ *
+ * @internal
+ */
+export type RouteLocationPathRaw<
+ RouteMap extends RouteStaticPathMapGeneric = RouteStaticPathMapGeneric
+> = RouteStaticPathMapGeneric extends RouteMap
+ ? // allows assigning a RouteLocationRaw to RouteLocationPat
+ RouteQueryAndHash & LocationAsPath & RouteLocationOptions
+ : {
+ [K in Extract<keyof RouteMap, string>]: RouteQueryAndHash &
+ LocationAsPath<RouteMap[K]['path']> &
+ RouteLocationOptions
+ }[Extract<keyof RouteMap, string>]
export interface RouteLocationMatched extends RouteRecordNormalized {
// components cannot be Lazy<RouteComponent>
RouteRecordRaw,
RouteRecordName,
} from '.'
-import type { _JoinPath, ParamsFromPath, ParamsRawFromPath } from './paths'
+import type {
+ _JoinPath,
+ ParamsFromPath,
+ ParamsRawFromPath,
+ PathFromParams,
+} from './paths'
+import { LiteralUnion } from './utils'
+/**
+ * Creates a map with each named route as a properties. Each property contains the type of the params in raw and
+ * normalized versions as well as the raw path.
+ * @internal
+ */
export type RouteNamedMap<
Routes extends Readonly<RouteRecordRaw[]>,
Prefix extends string = ''
> = Routes extends readonly [infer R, ...infer Rest]
? Rest extends Readonly<RouteRecordRaw[]>
- ? (R extends _RouteNamedRecordBaseInfo<
+ ? (R extends _RouteRecordNamedBaseInfo<
infer Name,
infer Path,
infer Children
// END: 1
}
-export interface _RouteNamedRecordBaseInfo<
+/**
+ * Type that adds valid semi literal paths to still enable autocomplete while allowing proper paths
+ */
+type _PathForAutocomplete<P extends string> = P extends `${string}:${string}`
+ ? LiteralUnion<P, PathFromParams<P>>
+ : P
+
+/**
+ * @internal
+ */
+export type _PathWithHash<P extends string> = `${P}#${string}`
+
+/**
+ * @internal
+ */
+export type _PathWithQuery<P extends string> = `${P}?${string}`
+
+/**
+ * @internal
+ */
+export type _FullPath<P extends string> = LiteralUnion<
+ P,
+ _PathWithHash<P> | _PathWithQuery<P>
+>
+
+/**
+ * @internal
+ */
+export type RouteStaticPathMap<
+ Routes extends Readonly<RouteRecordRaw[]>,
+ Prefix extends string = ''
+> = Routes extends readonly [infer R, ...infer Rest]
+ ? Rest extends Readonly<RouteRecordRaw[]>
+ ? (R extends _RouteRecordNamedBaseInfo<
+ infer _Name,
+ infer Path,
+ infer Children
+ >
+ ? {
+ // TODO: add | ${string} for params
+ // TODO: add extra type to append ? and # variants
+ [P in Path as _JoinPath<Prefix, Path>]: {
+ path: _PathForAutocomplete<_JoinPath<Prefix, Path>>
+ fullPath: _FullPath<_PathForAutocomplete<_JoinPath<Prefix, Path>>>
+ }
+ } & (Children extends Readonly<RouteRecordRaw[]> // Recurse children
+ ? RouteStaticPathMap<Children, _JoinPath<Prefix, Path>>
+ : {
+ // NO_CHILDREN: 1
+ })
+ : never) & // R must be a valid route record
+ // recurse children
+ RouteStaticPathMap<Rest, Prefix>
+ : {
+ // EMPTY: 1
+ }
+ : {
+ // END: 1
+ }
+
+/**
+ * Important information in a Named Route Record
+ * @internal
+ */
+export interface _RouteRecordNamedBaseInfo<
Name extends RouteRecordName = RouteRecordName, // we don't care about symbols
Path extends string = string,
Children extends Readonly<RouteRecordRaw[]> = Readonly<RouteRecordRaw[]>
/**
* Generic map of named routes from a list of route records.
+ *
+ * @internal
*/
export type RouteNamedMapGeneric = Record<RouteRecordName, RouteNamedInfo>
+/**
+ * Generic map of routes paths from a list of route records.
+ *
+ * @internal
+ */
+export type RouteStaticPathMapGeneric = Record<
+ string,
+ { path: string; fullPath: string }
+>
+
/**
* Relevant information about a named route record to deduce its params.
+ * @internal
*/
export interface RouteNamedInfo<
Path extends string = string,
*/
export type PathFromParams<
P extends string,
- PO extends ParamsFromPath<P>
+ PO extends ParamsFromPath<P> = ParamsFromPath<P>
> = string extends P ? string : _BuildPath<_RemoveRegexpFromParam<P>, PO>
/**
--- /dev/null
+export type LiteralUnion<LiteralType, BaseType extends string = string> =
+ | LiteralType
+ | (BaseType & Record<never, never>)
const r2 = createRouter({
history: createWebHistory(),
routes: [
+ { path: '/', component },
+ { path: '/foo', component },
{ path: '/users/:id', name: 'UserDetails', component },
{ path: '/no-name', /* no name */ components },
{
r2[method]({ name: routeName })
// @ts-expect-error: but not other symbols
r2[method]({ name: Symbol() })
- // any path is still valid
- r2[method]('/path')
- r2.push('/path')
- r2.replace('/path')
// relative push can have any of the params
r2[method]({ params: { a: 2 } })
r2[method]({ params: {} })
// FIXME: is it possible to support this version
// @ts-expect-error: does not accept any params
r2[method]({ name: 'nested', params: { id: 2 } })
+
+ // paths
+ r2[method]({ path: '/nested' })
+ r2[method]({ path: '/nested/a/b' })
+ // @ts-expect-error
+ r2[method]({ path: '' })
+ // @ts-expect-error
+ r2[method]({ path: '/nope' })
+ // @ts-expect-error
+ r2[method]({ path: '/no-name?query' })
+ // @ts-expect-error
+ r2[method]({ path: '/no-name#hash' })
+
+ r2[method]('/nested')
+ r2[method]('/nested/a/b')
+ // @ts-expect-error
+ r2[method]('')
+ // @ts-expect-error
+ r2[method]('/nope')
+ r2[method]('/no-name?query')
+ r2[method]('/no-name#hash')
}
// NOTE: not possible if we use the named routes as the point is to provide valid routes only