@@ -117,10 +117,6 @@ export const createIonRouter = (
117117 * instead, so there is no failure to inspect there and the staged state
118118 * would survive. The error is re-thrown to the app the same way it was
119119 * before, so navigation outcomes are unchanged.
120- *
121- * A guard that returns a location is still not covered, because that
122- * redirects rather than fails and afterEach is never called for the original
123- * navigation.
124120 */
125121 addErrorHandler ( ( error : unknown , to : RouteLocationNormalized ) => {
126122 discardStagedStateFor ( to ) ;
@@ -183,20 +179,71 @@ export const createIonRouter = (
183179 incomingRouteParamsUnclaimed = false ;
184180 } ;
185181
182+ /**
183+ * Whether `to` is a guard redirect of the navigation that claimed `owner`.
184+ * vue-router points `redirectedFrom` at the first location in a redirect
185+ * chain, so an owner that a `redirect:` record led to is matched on its own
186+ * `redirectedFrom`.
187+ */
188+ const isGuardRedirectOf = (
189+ owner : RouteLocationNormalized | undefined ,
190+ to : RouteLocationNormalized
191+ ) =>
192+ owner !== undefined &&
193+ to . redirectedFrom !== undefined &&
194+ ( owner . redirectedFrom ?? owner ) === to . redirectedFrom ;
195+
196+ /**
197+ * Whether `to` was redirected by a guard rather than a `redirect:` record.
198+ * This can't tell a record redirect apart from one a leave guard redirected
199+ * again afterwards.
200+ */
201+ const isRedirectedByGuard = ( to : RouteLocationNormalized ) => {
202+ const origin = to . redirectedFrom ;
203+
204+ return (
205+ origin !== undefined &&
206+ origin . matched [ origin . matched . length - 1 ] ?. redirect === undefined
207+ ) ;
208+ } ;
209+
186210 /**
187211 * The navigation that starts first after params are staged is the one they
188212 * were staged for, so it takes ownership of them here. Registered before any
189213 * guard the app adds so that it still runs when one of those aborts.
214+ *
215+ * A guard that returns a location starts a new navigation and the original
216+ * never reaches afterEach or onError, so we discard what it claimed here
217+ * instead. Leave guards run before this hook, so a navigation they redirect
218+ * hasn't claimed anything yet and its state is discarded unclaimed.
190219 */
191220 router . beforeEach ( ( to : RouteLocationNormalized ) => {
221+ if ( isGuardRedirectOf ( currentNavigationInfoOwner , to ) ) {
222+ clearNavigationInfo ( ) ;
223+ }
224+
225+ if ( isGuardRedirectOf ( incomingRouteParamsOwner , to ) ) {
226+ clearStagedParams ( ) ;
227+ }
228+
229+ const redirectedByGuard = isRedirectedByGuard ( to ) ;
230+
192231 if ( incomingRouteParamsUnclaimed ) {
193- incomingRouteParamsOwner = to ;
194- incomingRouteParamsUnclaimed = false ;
232+ if ( redirectedByGuard ) {
233+ clearStagedParams ( ) ;
234+ } else {
235+ incomingRouteParamsOwner = to ;
236+ incomingRouteParamsUnclaimed = false ;
237+ }
195238 }
196239
197240 if ( currentNavigationInfoUnclaimed ) {
198- currentNavigationInfoOwner = to ;
199- currentNavigationInfoUnclaimed = false ;
241+ if ( redirectedByGuard ) {
242+ clearNavigationInfo ( ) ;
243+ } else {
244+ currentNavigationInfoOwner = to ;
245+ currentNavigationInfoUnclaimed = false ;
246+ }
200247 }
201248 } ) ;
202249
@@ -219,18 +266,22 @@ export const createIonRouter = (
219266 * Both are matched on the navigation that owns them rather than on where it
220267 * was heading, because two navigations can head for the same path and a path
221268 * cannot tell them apart. State still unclaimed belongs to this navigation,
222- * since nothing has started since it was staged.
269+ * since nothing has started since it was staged. A guard redirect that fails
270+ * before reaching beforeEach, like one to the current page, is matched
271+ * through the navigation it redirected.
223272 */
224273 const discardStagedStateFor = ( to : RouteLocationNormalized ) => {
225274 const deltaIsForThisNavigation =
226275 currentNavigationInfoOwner === undefined
227276 ? currentNavigationInfoUnclaimed
228- : currentNavigationInfoOwner === to ;
277+ : currentNavigationInfoOwner === to ||
278+ isGuardRedirectOf ( currentNavigationInfoOwner , to ) ;
229279
230280 const paramsAreForThisNavigation =
231281 incomingRouteParamsOwner === undefined
232282 ? incomingRouteParamsUnclaimed
233- : incomingRouteParamsOwner === to ;
283+ : incomingRouteParamsOwner === to ||
284+ isGuardRedirectOf ( incomingRouteParamsOwner , to ) ;
234285
235286 if ( deltaIsForThisNavigation ) {
236287 clearNavigationInfo ( ) ;
0 commit comments