diff --git a/README.md b/README.md index 4dbf106..205c8f6 100644 --- a/README.md +++ b/README.md @@ -280,9 +280,9 @@ Where possible, it also smooths out the differences between versions: these natively, so skip the extension there. - **Features that HTMX 4 dropped.** Where HTMX 4 has a close equivalent, the toolkit uses it. For example, HTMX 4 has no `HX-Trigger-After-Swap` and `HX-Trigger-After-Settle` headers, - so events with these timings are sent in `HX-Trigger` and fire when the request completes. + so events with these timings are sent in `HX-Trigger`, which HTMX 4 handles after the swap and settle steps. -Some differences can't be hidden. For example, in HTMX 4 an event can't wait for the settle step. +Some differences can't be hidden. For example, in HTMX 4 a response event can't fire before the swap. If you aren't sure which version to pick, see [Choose an HTMX version](https://rameel.github.io/ramstack.htmxtoolkit/articles/choosing-version.html). For all the differences, see [Version compatibility](https://rameel.github.io/ramstack.htmxtoolkit/articles/version-compatibility.html). diff --git a/docs/articles/responses.md b/docs/articles/responses.md index a084889..c1b698b 100644 --- a/docs/articles/responses.md +++ b/docs/articles/responses.md @@ -205,14 +205,17 @@ The `hx-trigger` listener can still refresh a component while JavaScript handles ### Timing and repeated events HTMX 1.x and 2.x distinguish receive, after-swap, and after-settle response headers. -HTMX 4.x delivers these Toolkit events through `HX-Trigger` when the request completes, after the swap when one occurs. +HTMX 4.x delivers these Toolkit events through `HX-Trigger` when the request completes, +after the swap and settle steps when a swap occurs. See [Version compatibility](version-compatibility.md#response-events). -Calling `TriggerEvent` several times adds events to the same response, and the browser receives them in call order. -A JSON header cannot hold the same name twice, so when a name repeats within one header, all events of that header -are sent under the reserved `rs:event` name and the [Toolkit script](antiforgery.md#what-the-toolkit-script-does) -replays them in order. Keep the script loaded when using repeated event names: without it, none of the events -in that header are triggered. Do not use the name `rs:event`. +Calling `TriggerEvent` several times adds events to the same response, and the browser receives the events +of each header in call order. A JSON header cannot hold the same name twice, so when a name repeats within +one header, all events of that header are sent under the reserved `rs:event` name and +the [Toolkit script](antiforgery.md#what-the-toolkit-script-does) replays them in order. +Keep the script loaded when using repeated event names: without it, none of the events +in that header are triggered. In HTMX 4.x all timings share `HX-Trigger`, so this also applies to a name +added with different timings. Do not use the name `rs:event`. ### Serialize details with Native AOT diff --git a/docs/articles/troubleshooting.md b/docs/articles/troubleshooting.md index 5b4c63d..2fc6974 100644 --- a/docs/articles/troubleshooting.md +++ b/docs/articles/troubleshooting.md @@ -237,8 +237,15 @@ If no method is present, the URL Tag Helper generates `hx-get`. ## Event timing differs after moving to HTMX 4 HTMX 1.x and 2.x have separate trigger response headers for receive, after swap, and after settle. -HTMX 4.x receives Toolkit events through `HX-Trigger` at request completion. -Review uses of `HtmxTriggerTiming.AfterSettle` during migration. +HTMX 4.x receives all Toolkit events through `HX-Trigger` when the request completes, after the swap and settle steps. +During migration, review: + +- `HtmxTriggerTiming.Receive` events whose handlers must run before the swap. + Move that logic to an `htmx:before:swap` listener. +- `AfterSwap` and `AfterSettle` events that must not fire when the response is not swapped, + for example with status code 204. + +See [Response events](version-compatibility.md#response-events). ## A response event does not refresh another component diff --git a/docs/articles/version-compatibility.md b/docs/articles/version-compatibility.md index 488c79e..2caa1d0 100644 --- a/docs/articles/version-compatibility.md +++ b/docs/articles/version-compatibility.md @@ -74,8 +74,23 @@ Review the target version's guide rather than copying a configuration delegate u ## Response events HTMX 1.x and 2.x distinguish `HX-Trigger`, `HX-Trigger-After-Swap`, and `HX-Trigger-After-Settle`. -HTMX 4.x emits Toolkit events through `HX-Trigger` when the request completes. -An `AfterSettle` timing therefore cannot retain its separate V1/V2 timing under V4. +HTMX 4.x has only `HX-Trigger`, so the Toolkit emits events of every `HtmxTriggerTiming` through it. + +| Timing | HTMX 1.x and 2.x | HTMX 4.x | +|---|---|---| +| `Receive` | When the response is received, before the swap | When the request completes | +| `AfterSwap` | After the swap step, only if the response is swapped | When the request completes | +| `AfterSettle` | After the settle step, only if the response is swapped | When the request completes | + +HTMX 4.x completes the request after the swap and settle steps, so `AfterSwap` and `AfterSettle` events +still fire after the DOM is updated. Two differences remain: + +- `Receive` events no longer fire before the swap. To act before the swap, handle `htmx:before:swap` on the client. +- `AfterSwap` and `AfterSettle` events also fire when the response is not swapped, + for example with status code 204 or an `HX-Location` header. + +Because all timings share one header, events fire in call order regardless of timing, and a name added +with different timings counts as repeated. See [Timing and repeated events](responses.md#timing-and-repeated-events). ## Polling diff --git a/src/Ramstack.HtmxToolkit/HtmxResponse.cs b/src/Ramstack.HtmxToolkit/HtmxResponse.cs index fc2e33b..1ea962b 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponse.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponse.cs @@ -223,14 +223,16 @@ public HtmxResponse Reselect(string value) => /// /// /// - /// In HTMX 4.x, every value is emitted through - /// HX-Trigger and runs when the request completes (after the swap whenever one is performed). + /// HTMX 4.x has only the HX-Trigger header, so every value + /// is emitted through it. These events fire when the request completes, which is after the swap + /// and settle steps whenever a swap is performed. /// See PR #3900. /// /// - /// Events are triggered in the order they are added. If an event name is added more than once - /// for the same header, all events of that header are replayed by the Toolkit script, - /// which must be loaded for any of them to be triggered. + /// Events of one header are triggered in the order they are added. If an event name is added + /// more than once for the same header, all events of that header are replayed by the Toolkit script, + /// which must be loaded for any of them to be triggered. In HTMX 4.x, this also applies + /// to a name added with different timings. /// /// public HtmxResponse TriggerEvent(string eventName, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive) => @@ -242,14 +244,16 @@ public HtmxResponse TriggerEvent(string eventName, HtmxTriggerTiming timing = Ht /// /// /// - /// In HTMX 4.x, every value is emitted through HX-Trigger - /// and runs when the request completes (after the swap whenever one is performed). + /// HTMX 4.x has only the HX-Trigger header, so every value + /// is emitted through it. These events fire when the request completes, which is after the swap + /// and settle steps whenever a swap is performed. /// See PR #3900. /// /// - /// Events are triggered in the order they are added. If an event name is added more than once - /// for the same header, all events of that header are replayed by the Toolkit script, - /// which must be loaded for any of them to be triggered. + /// Events of one header are triggered in the order they are added. If an event name is added + /// more than once for the same header, all events of that header are replayed by the Toolkit script, + /// which must be loaded for any of them to be triggered. In HTMX 4.x, this also applies + /// to a name added with different timings. /// /// /// The event name to trigger. @@ -282,14 +286,16 @@ static HtmxResponse TriggerEventImpl(HtmxResponse response, string eventName, ob /// /// /// - /// In HTMX 4.x, every value is emitted through HX-Trigger - /// and runs when the request completes (after the swap whenever one is performed). + /// HTMX 4.x has only the HX-Trigger header, so every value + /// is emitted through it. These events fire when the request completes, which is after the swap + /// and settle steps whenever a swap is performed. /// See PR #3900. /// /// - /// Events are triggered in the order they are added. If an event name is added more than once - /// for the same header, all events of that header are replayed by the Toolkit script, - /// which must be loaded for any of them to be triggered. + /// Events of one header are triggered in the order they are added. If an event name is added + /// more than once for the same header, all events of that header are replayed by the Toolkit script, + /// which must be loaded for any of them to be triggered. In HTMX 4.x, this also applies + /// to a name added with different timings. /// /// public HtmxResponse TriggerEvent(string eventName, T detail, JsonTypeInfo jsonTypeInfo, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive) => diff --git a/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs b/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs index b1d9a3d..70f0ee5 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs @@ -138,7 +138,9 @@ public string Reselect /// /// HTMX 1.x and 2.x trigger these events when the response is received, /// whereas HTMX 4.x triggers them when the request completes - /// (after the swap whenever one is performed). + /// (after the swap and settle steps whenever a swap is performed). + /// In HTMX 4.x, the list also includes the events added with other timings, + /// because all timings share the HX-Trigger header. /// /// public IReadOnlyList>? Trigger => PendingEvents.TryGet(_response)?.GetEvents(HtmxTriggerTiming.Receive); @@ -155,9 +157,9 @@ public string Reselect /// the property returns . /// /// - /// In HTMX 4.x, assigned events are accumulated in - /// and emitted through HX-Trigger when the request completes - /// (after the swap whenever one is performed). + /// HTMX 4.x has no HX-Trigger-After-Swap header, so these events are accumulated + /// in and emitted through HX-Trigger when the request completes + /// (after the swap and settle steps whenever a swap is performed). /// /// public IReadOnlyList>? TriggerAfterSwap => PendingEvents.TryGet(_response)?.GetEvents(HtmxTriggerTiming.AfterSwap); @@ -174,10 +176,9 @@ public string Reselect /// the property returns . /// /// - /// In HTMX 4.x, assigned events are accumulated in - /// and emitted through HX-Trigger when the request completes - /// (after the swap whenever one is performed); the requested after-settle timing - /// cannot be preserved. + /// HTMX 4.x has no HX-Trigger-After-Settle header, so these events are accumulated + /// in and emitted through HX-Trigger when the request completes + /// (after the swap and settle steps whenever a swap is performed). /// /// public IReadOnlyList>? TriggerAfterSettle => PendingEvents.TryGet(_response)?.GetEvents(HtmxTriggerTiming.AfterSettle); diff --git a/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs b/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs index 80de07a..e705dca 100644 --- a/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs +++ b/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs @@ -9,10 +9,11 @@ public enum HtmxTriggerTiming /// Maps to the HX-Trigger header. /// /// - /// HTMX 1.x and 2.x trigger these events when the response is received. + /// HTMX 1.x and 2.x trigger these events when the response is received, before the swap. /// - /// HTMX 4.x triggers them when the request completes, which is after the swap - /// whenever one is performed. + /// HTMX 4.x triggers them when the request completes, which is after the swap and settle steps + /// whenever a swap is performed. To act before the swap in HTMX 4.x, handle htmx:before:swap + /// on the client. /// See PR #3900. /// /// @@ -22,10 +23,14 @@ public enum HtmxTriggerTiming /// Maps to the HX-Trigger-After-Swap header, which triggers events after the swap step. /// /// - /// HTMX 1.x and 2.x emit these events through HX-Trigger-After-Swap. /// - /// HTMX 4.x emits them through HX-Trigger, which also fires when the request - /// completes (after the swap whenever one is performed). + /// HTMX 1.x and 2.x trigger these events only when the response is swapped, + /// so they do not fire for a status code such as 204. + /// + /// + /// HTMX 4.x has no HX-Trigger-After-Swap header, so these events are emitted through + /// HX-Trigger. They fire when the request completes: after the swap and settle steps + /// whenever a swap is performed, and also when the response is not swapped. /// See PR #3900. /// /// @@ -36,10 +41,14 @@ public enum HtmxTriggerTiming /// after the settle step. /// /// - /// HTMX 1.x and 2.x emit these events through HX-Trigger-After-Settle. /// - /// HTMX 4.x emits them through HX-Trigger when the request completes, i.e. after - /// the swap whenever one is performed; the requested after-settle timing cannot be preserved. + /// HTMX 1.x and 2.x trigger these events only when the response is swapped, + /// so they do not fire for a status code such as 204. + /// + /// + /// HTMX 4.x has no HX-Trigger-After-Settle header, so these events are emitted through + /// HX-Trigger. They fire when the request completes: after the swap and settle steps + /// whenever a swap is performed, and also when the response is not swapped. /// See PR #3900. /// ///