From c7d5cdcfaa121778093f24feac0f340051b49b7a Mon Sep 17 00:00:00 2001 From: Noam Rosenthal Date: Tue, 11 Mar 2025 09:29:53 +0000 Subject: [PATCH 1/6] Update README.md to use precommit handlers instead of `after-transition` --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fe9d8c0..8b90a1d 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ backButtonEl.addEventListener("click", () => { - [Focus management](#focus-management) - [Scrolling to fragments and scroll resetting](#scrolling-to-fragments-and-scroll-resetting) - [Scroll position restoration](#scroll-position-restoration) - - [Deferred commit](#deferred-commit) + - [Precommit handlers](#precommit-handlers) - [Redirects during deferred commit](#redirects-during-deferred-commit) - [Transitional time after navigation interception](#transitional-time-after-navigation-interception) - [Example: handling failed navigations](#example-handling-failed-navigations) @@ -691,28 +691,29 @@ Some more details on how the navigation API handles scrolling with `"traverse"` - By default, any navigations which are intercepted with `navigateEvent.intercept()` will _ignore_ the value of `history.scrollRestoration` from the classic history API. This allows developers to use `history.scrollRestoration` for controlling cross-document scroll restoration, while using the more-granular option to `intercept()` to control individual same-document navigations. -#### Deferred commit +#### Precommit handlers -The default behavior of immediately "committing" (i.e., updating `location.href` and `navigation.currentEntry`) works well for most situations, but some developers may find they do not want to immediately update the URL, and may want to retain the option of aborting the navigation without needing to rollback a URL update or cancel-and-restart. This behavior can be customized using `intercept()`'s `commit` option: +The default behavior of immediately "committing" (i.e., updating `location.href` and `navigation.currentEntry`) works well for most situations, but some developers may find they do not want to immediately update the URL, and may want to retain the option of aborting the navigation without needing to rollback a URL update or cancel-and-restart. This behavior can be customized passing a `precommitHandler` callback alongside or instead of the `handler` callback: -- `e.intercept({ handler, commit: "immediate" })`: the default behavior, immediately commit the navigation and update `location.href` and `navigation.currentEntry`. -- `e.intercept({ handler, commit: "after-transition" })`: start the navigation (e.g., show a loading spinner if the UI has one), but do not immediately commit. +- `e.intercept({ handler })`: the default behavior, immediately commit the navigation and update `location.href` and `navigation.currentEntry`. +- `e.intercept({ precommitHandler })`: start the navigation (e.g., show a loading spinner if the UI has one), but do not immediately commit. -When deferred commit is used, the navigation will commit (and a `committed` promise will resolve if present) when `e.commit()` is called. If any handler(s) passed to `intercept()` fulfill, and `e.commit()` has not yet been called, we will fallback to committing just before any `finish` promise resolves and `navigatesuccess` is fired. +When precommit handlers are used, the navigation will commit (and a `committed` promise will resolve if present) once all those handlers are fulfilled. Unlike the ordinary `handler`, the `precommitHandler` callbacks are called in sequence - the next `precommit` handler is invoked only when the previous one is fulfilled. That is due to the fact that a precommit handler can asynchronously abort the navigation altogether or redirect the URL, and the next precommit handler should respond to the new state. -If a handler passed to `intercept()` rejects before `e.commit()` is called, then the navigation will be treated as canceled (both `committed` and `finished` promises will reject, and no URL update will occur). If a handler passed to `intercept()` rejects after `e.commit()` is called, the behavior will match a rejected promise in immediate commit mode (i.e., the `committed` promise will fulfill, the `finished` promise will reject, and the URL will update). +If a `precommitHandler` passed to `intercept()` rejects, then the navigation will be treated as canceled (both `committed` and `finished` promises will reject, and no URL update will occur). -Because deferred commit can be used to cancel the navigation before the URL updates, it is only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus deferred commit is not available. +Because deferred commit can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. #### Redirects during deferred commit -If the `{ commit: "after-transition" }` option is passed to `navigateEvent.intercept()`, then an additional method is available on the `NavigateEvent`: `redirect(url)`. This updates the eventual destination of the `"push"` or `"replace"` navigation. An example usage is as follows: +The `precommitHandler` callback accepts an argument, which is a `controller` that can perform certain actions on the precommitted navigation, in particular redirecting. This updates the eventual destination of the `"push"` or `"replace"` navigation, and restarts the sequence of calling the precommit handlers. An example usage is as follows ```js navigation.addEventListener("navigate", e => { - e.intercept({ async handler() { + e.intercept({ async precommitHandler(controller) { if (await isLoginGuarded(e.destination)) { - e.redirect("/login"); + controller.redirect("/login"); + return; } // Render e.destination, keeping in mind e.destination might be updated. From 21421c354dd61be90b984a32c6c9eb54f2daceba Mon Sep 17 00:00:00 2001 From: Noam Rosenthal Date: Tue, 11 Mar 2025 13:34:25 +0000 Subject: [PATCH 2/6] Update README.md Co-authored-by: Bramus --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b90a1d..2d14ee5 100644 --- a/README.md +++ b/README.md @@ -702,7 +702,7 @@ When precommit handlers are used, the navigation will commit (and a `committed` If a `precommitHandler` passed to `intercept()` rejects, then the navigation will be treated as canceled (both `committed` and `finished` promises will reject, and no URL update will occur). -Because deferred commit can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. +Because precommit handlers can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. #### Redirects during deferred commit From 63c5e180b1a8ea65425fa4963a5332f6645690cb Mon Sep 17 00:00:00 2001 From: Noam Rosenthal Date: Wed, 12 Mar 2025 08:19:34 +0000 Subject: [PATCH 3/6] Update README.md Co-authored-by: Domenic Denicola --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d14ee5..5d10678 100644 --- a/README.md +++ b/README.md @@ -698,7 +698,7 @@ The default behavior of immediately "committing" (i.e., updating `location.href` - `e.intercept({ handler })`: the default behavior, immediately commit the navigation and update `location.href` and `navigation.currentEntry`. - `e.intercept({ precommitHandler })`: start the navigation (e.g., show a loading spinner if the UI has one), but do not immediately commit. -When precommit handlers are used, the navigation will commit (and a `committed` promise will resolve if present) once all those handlers are fulfilled. Unlike the ordinary `handler`, the `precommitHandler` callbacks are called in sequence - the next `precommit` handler is invoked only when the previous one is fulfilled. That is due to the fact that a precommit handler can asynchronously abort the navigation altogether or redirect the URL, and the next precommit handler should respond to the new state. +When precommit handlers are used, the navigation will commit (and a `committed` promise will resolve if present) once all those handlers are fulfilled. Unlike the ordinary `handler`, the `precommitHandler` callbacks are called in sequence—the next `precommit` handler is invoked only when the previous one is fulfilled. That is due to the fact that a precommit handler can asynchronously abort the navigation altogether or redirect the URL, and the next precommit handler should respond to the new state. If a `precommitHandler` passed to `intercept()` rejects, then the navigation will be treated as canceled (both `committed` and `finished` promises will reject, and no URL update will occur). From 80983158e148ae32e37fc0ec3a4f54edc24b5aab Mon Sep 17 00:00:00 2001 From: Noam Rosenthal Date: Wed, 12 Mar 2025 08:21:58 +0000 Subject: [PATCH 4/6] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d10678..ff2d2df 100644 --- a/README.md +++ b/README.md @@ -698,11 +698,13 @@ The default behavior of immediately "committing" (i.e., updating `location.href` - `e.intercept({ handler })`: the default behavior, immediately commit the navigation and update `location.href` and `navigation.currentEntry`. - `e.intercept({ precommitHandler })`: start the navigation (e.g., show a loading spinner if the UI has one), but do not immediately commit. +The object passed to intercept can include both a `handler` and a `precommitHandler`. If both are included, they are called individually at the appropriate phase. + When precommit handlers are used, the navigation will commit (and a `committed` promise will resolve if present) once all those handlers are fulfilled. Unlike the ordinary `handler`, the `precommitHandler` callbacks are called in sequence—the next `precommit` handler is invoked only when the previous one is fulfilled. That is due to the fact that a precommit handler can asynchronously abort the navigation altogether or redirect the URL, and the next precommit handler should respond to the new state. If a `precommitHandler` passed to `intercept()` rejects, then the navigation will be treated as canceled (both `committed` and `finished` promises will reject, and no URL update will occur). -Because precommit handlers can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. +Because precommit handlers can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. calling `intercept()` with a `precommitHandler` on a non-cancelable event would throw a `SecurityError`. #### Redirects during deferred commit From f04e37443fa5ed4537eaab128a64cfc4182d0d88 Mon Sep 17 00:00:00 2001 From: Noam Rosenthal Date: Wed, 12 Mar 2025 08:23:45 +0000 Subject: [PATCH 5/6] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ff2d2df..cea86b3 100644 --- a/README.md +++ b/README.md @@ -712,14 +712,17 @@ The `precommitHandler` callback accepts an argument, which is a `controller` tha ```js navigation.addEventListener("navigate", e => { - e.intercept({ async precommitHandler(controller) { - if (await isLoginGuarded(e.destination)) { - controller.redirect("/login"); - return; + e.intercept({ + async precommitHandler(controller) { + if (await isLoginGuarded(e.destination)) { + controller.redirect("/login"); + } } - // Render e.destination, keeping in mind e.destination might be updated. - } }); + async handler() { + // apply committed navigation state to document + } +}); }); ``` From 02f8bab3524cf47e7f8a46a8bae92c7ead792416 Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Thu, 13 Mar 2025 14:49:09 +0900 Subject: [PATCH 6/6] Nits --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cea86b3..8c5be90 100644 --- a/README.md +++ b/README.md @@ -704,7 +704,7 @@ When precommit handlers are used, the navigation will commit (and a `committed` If a `precommitHandler` passed to `intercept()` rejects, then the navigation will be treated as canceled (both `committed` and `finished` promises will reject, and no URL update will occur). -Because precommit handlers can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. calling `intercept()` with a `precommitHandler` on a non-cancelable event would throw a `SecurityError`. +Because precommit handlers can be used to cancel the navigation before the URL updates, they are only available when `e.cancelable` is true. See [above](#restrictions-on-firing-canceling-and-responding) for details on when `e.cancelable` is set to false, and thus precommit handlers are not available. Calling `intercept()` with a `precommitHandler` on a non-cancelable event would throw a `"SecurityError"` `DOMException`. #### Redirects during deferred commit @@ -721,7 +721,7 @@ navigation.addEventListener("navigate", e => { async handler() { // apply committed navigation state to document - } + } }); }); ```