diff --git a/docs/router/framework/react/api/router/RouterEventsType.md b/docs/router/framework/react/api/router/RouterEventsType.md index 0d31f05ce38..cb94a2e4830 100644 --- a/docs/router/framework/react/api/router/RouterEventsType.md +++ b/docs/router/framework/react/api/router/RouterEventsType.md @@ -13,6 +13,7 @@ type RouterEvents = { toLocation: ParsedLocation pathChanged: boolean hrefChanged: boolean + hashChanged: boolean } onBeforeLoad: { type: 'onBeforeLoad' @@ -20,6 +21,7 @@ type RouterEvents = { toLocation: ParsedLocation pathChanged: boolean hrefChanged: boolean + hashChanged: boolean } onLoad: { type: 'onLoad' @@ -27,6 +29,7 @@ type RouterEvents = { toLocation: ParsedLocation pathChanged: boolean hrefChanged: boolean + hashChanged: boolean } onResolved: { type: 'onResolved' @@ -34,6 +37,7 @@ type RouterEvents = { toLocation: ParsedLocation pathChanged: boolean hrefChanged: boolean + hashChanged: boolean } onBeforeRouteMount: { type: 'onBeforeRouteMount' @@ -41,6 +45,7 @@ type RouterEvents = { toLocation: ParsedLocation pathChanged: boolean hrefChanged: boolean + hashChanged: boolean } onInjectedHtml: { type: 'onInjectedHtml' @@ -50,6 +55,45 @@ type RouterEvents = { type: 'onRendered' fromLocation?: ParsedLocation toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean + } + onViewTransitionStart: { + type: 'onViewTransitionStart' + transition: ViewTransition + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean + } + onViewTransitionReady: { + type: 'onViewTransitionReady' + transition: ViewTransition + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean + } + onViewTransitionUpdateCallbackDone: { + type: 'onViewTransitionUpdateCallbackDone' + transition: ViewTransition + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean + } + onViewTransitionFinish: { + type: 'onViewTransitionFinish' + transition: ViewTransition + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean } } ``` @@ -60,7 +104,7 @@ Once an event is emitted, the following properties will be present on the event ### `type` property -- Type: `onBeforeNavigate | onBeforeLoad | onLoad | onBeforeRouteMount | onResolved` +- Type: `onBeforeNavigate | onBeforeLoad | onLoad | onBeforeRouteMount | onResolved | onRendered | onViewTransitionStart | onViewTransitionReady | onViewTransitionUpdateCallbackDone | onViewTransitionFinish` - The type of the event - This is useful for discriminating between events in a listener function. @@ -84,6 +128,18 @@ Once an event is emitted, the following properties will be present on the event - Type: `boolean` - `true` if the href has changed between the `fromLocation` and `toLocation`. +### `hashChanged` property + +- Type: `boolean` +- `true` if the hash has changed between the `fromLocation` and `toLocation`. + +### `transition` property + +- Type: `ViewTransition` +- Available on: `onViewTransitionStart`, `onViewTransitionReady`, `onViewTransitionUpdateCallbackDone`, `onViewTransitionFinish` +- The [ViewTransition](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) object representing the view transition in progress. +- This property allows you to interact with the view transition lifecycle, including access to promises like `ready`, `updateCallbackDone`, and `finished`. + ## Example ```tsx diff --git a/packages/router-core/src/index.ts b/packages/router-core/src/index.ts index b86a1ed67f5..fba7ce7a823 100644 --- a/packages/router-core/src/index.ts +++ b/packages/router-core/src/index.ts @@ -208,6 +208,7 @@ export { } from './router' export type { + LocationChangeInfo, ViewTransitionOptions, TrailingSlashOption, Register, diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 962c536045a..238a7b5a446 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -544,6 +544,11 @@ type NavigationEventInfo = { hashChanged: boolean } +export type ViewTransitionEventInfo = { + // @ts-ignore -- ViewTransition support since ts 5.6 + transition: ViewTransition +} + export interface RouterEvents { onBeforeNavigate: { type: 'onBeforeNavigate' @@ -563,6 +568,22 @@ export interface RouterEvents { onRendered: { type: 'onRendered' } & NavigationEventInfo + onViewTransitionStart: { + type: 'onViewTransitionStart' + } & ViewTransitionEventInfo & + NavigationEventInfo + onViewTransitionReady: { + type: 'onViewTransitionReady' + } & ViewTransitionEventInfo & + NavigationEventInfo + onViewTransitionUpdateCallbackDone: { + type: 'onViewTransitionUpdateCallbackDone' + } & ViewTransitionEventInfo & + NavigationEventInfo + onViewTransitionFinish: { + type: 'onViewTransitionFinish' + } & ViewTransitionEventInfo & + NavigationEventInfo } export type RouterEvent = RouterEvents[keyof RouterEvents] @@ -766,16 +787,18 @@ export type AnyRouterWithContext = RouterCore< export type AnyRouter = RouterCore +export interface LocationChangeInfo { + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean +} + export interface ViewTransitionOptions { types: | Array - | ((locationChangeInfo: { - fromLocation?: ParsedLocation - toLocation: ParsedLocation - pathChanged: boolean - hrefChanged: boolean - hashChanged: boolean - }) => Array | false) + | ((locationChangeInfo: LocationChangeInfo) => Array | false) } // TODO where is this used? can we remove this? @@ -819,7 +842,7 @@ export type TrailingSlashOption = export function getLocationChangeInfo(routerState: { resolvedLocation?: ParsedLocation location: ParsedLocation -}) { +}): LocationChangeInfo { const fromLocation = routerState.resolvedLocation const toLocation = routerState.location const pathChanged = fromLocation?.pathname !== toLocation.pathname @@ -2088,22 +2111,21 @@ export class RouterCore< const next = this.latestLocation const prevLocation = this.state.resolvedLocation + const locationChangeInfo = getLocationChangeInfo({ + resolvedLocation: prevLocation, + location: next, + }) + if (!this.state.redirect) { this.emit({ type: 'onBeforeNavigate', - ...getLocationChangeInfo({ - resolvedLocation: prevLocation, - location: next, - }), + ...locationChangeInfo, }) } this.emit({ type: 'onBeforeLoad', - ...getLocationChangeInfo({ - resolvedLocation: prevLocation, - location: next, - }), + ...locationChangeInfo, }) await loadMatches({ @@ -2114,7 +2136,6 @@ export class RouterCore< updateMatch: this.updateMatch, // eslint-disable-next-line @typescript-eslint/require-await onReady: async () => { - // eslint-disable-next-line @typescript-eslint/require-await // Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition) this.startTransition(() => { this.startViewTransition(async () => { @@ -2256,21 +2277,21 @@ export class RouterCore< // TODO: Fix this when dom types are updated let startViewTransitionParams: any + const next = this.latestLocation + const prevLocation = this.state.resolvedLocation + + const locationChangeInfo = getLocationChangeInfo({ + resolvedLocation: prevLocation, + location: next, + }) + if ( typeof shouldViewTransition === 'object' && this.isViewTransitionTypesSupported ) { - const next = this.latestLocation - const prevLocation = this.state.resolvedLocation - const resolvedViewTransitionTypes = typeof shouldViewTransition.types === 'function' - ? shouldViewTransition.types( - getLocationChangeInfo({ - resolvedLocation: prevLocation, - location: next, - }), - ) + ? shouldViewTransition.types(locationChangeInfo) : shouldViewTransition.types if (resolvedViewTransitionTypes === false) { @@ -2286,7 +2307,33 @@ export class RouterCore< startViewTransitionParams = fn } - document.startViewTransition(startViewTransitionParams) + const transition = document.startViewTransition(startViewTransitionParams) + this.emit({ + type: 'onViewTransitionStart', + transition, + ...locationChangeInfo, + }) + transition.ready.finally(() => { + this.emit({ + type: 'onViewTransitionReady', + transition, + ...locationChangeInfo, + }) + }) + transition.updateCallbackDone.finally(() => { + this.emit({ + type: 'onViewTransitionUpdateCallbackDone', + transition, + ...locationChangeInfo, + }) + }) + transition.finished.finally(() => { + this.emit({ + type: 'onViewTransitionFinish', + transition, + ...locationChangeInfo, + }) + }) } else { fn() }