From d3872e4caafbcd29707da11c4d44db6610f0559f Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 4 Sep 2026 21:49:42 +0500 Subject: [PATCH 1/4] Correct comments --- .../Collections/SmallDictionary.cs | 6 +++--- src/Ramstack.HtmxToolkit/Configuration/HtmxConfig.cs | 2 +- .../Configuration/HtmxFetchMode.cs | 6 +++--- src/Ramstack.HtmxToolkit/Configuration/HtmxV1Config.cs | 4 ++-- src/Ramstack.HtmxToolkit/Configuration/HtmxV2Config.cs | 2 +- .../Configuration/ResponseHandlingConfig.cs | 2 +- src/Ramstack.HtmxToolkit/HtmxLocationOptions.cs | 2 +- src/Ramstack.HtmxToolkit/HtmxResponse.cs | 2 +- .../TagHelpers/HtmxRequestTagHelper.cs | 4 ++-- .../TagHelpers/HtmxUrlTagHelper.cs | 2 +- tests/Ramstack.HtmxToolkit.Tests/JsonHelper.cs | 4 ++-- tests/Ramstack.HtmxToolkit.Tests/TestHelper.cs | 10 +++++----- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs b/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs index 2dc9446..fd8df81 100644 --- a/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs +++ b/src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs @@ -615,7 +615,7 @@ internal Enumerator(SmallDictionary dictionary) public bool MoveNext() { // - // JIT generates redundant mov for inline ++i (pre-increment) + // The JIT generates a redundant mov instruction for inline ++i (pre-increment). // https://github.com/dotnet/runtime/issues/129532 // @@ -758,7 +758,7 @@ internal Enumerator(SmallDictionary dictionary) public bool MoveNext() { // - // JIT generates redundant mov for inline ++i (pre-increment) + // The JIT generates a redundant mov instruction for inline ++i (pre-increment). // https://github.com/dotnet/runtime/issues/129532 // @@ -826,7 +826,7 @@ internal Enumerator(SmallDictionary dictionary) public bool MoveNext() { // - // JIT generates redundant mov for inline ++i (pre-increment) + // The JIT generates a redundant mov instruction for inline ++i (pre-increment). // https://github.com/dotnet/runtime/issues/129532 // diff --git a/src/Ramstack.HtmxToolkit/Configuration/HtmxConfig.cs b/src/Ramstack.HtmxToolkit/Configuration/HtmxConfig.cs index 712743e..fd4f6e7 100644 --- a/src/Ramstack.HtmxToolkit/Configuration/HtmxConfig.cs +++ b/src/Ramstack.HtmxToolkit/Configuration/HtmxConfig.cs @@ -27,7 +27,7 @@ internal HtmxConfig(HtmxTargetVersion version) => TargetVersion = version; /// - /// Returns this configuration serialized as JSON, + /// Returns this configuration serialized as JSON. The result is /// cached and reused until the configuration changes. /// /// diff --git a/src/Ramstack.HtmxToolkit/Configuration/HtmxFetchMode.cs b/src/Ramstack.HtmxToolkit/Configuration/HtmxFetchMode.cs index c2fd9a0..bdd2a0a 100644 --- a/src/Ramstack.HtmxToolkit/Configuration/HtmxFetchMode.cs +++ b/src/Ramstack.HtmxToolkit/Configuration/HtmxFetchMode.cs @@ -6,9 +6,9 @@ namespace Ramstack.HtmxToolkit.Configuration; /// /// In HTMX 4.x this is passed as the mode option of the Fetch API. /// -/// In HTMX 1.x and 2.x (compatibility mode) this maps to the selfRequestsOnly -/// boolean configuration option, where yields -/// and any other value yields . +/// In HTMX 1.x and 2.x, the equivalent setting is the selfRequestsOnly +/// boolean configuration option, for which corresponds to +/// and any other value corresponds to . /// /// public enum HtmxFetchMode diff --git a/src/Ramstack.HtmxToolkit/Configuration/HtmxV1Config.cs b/src/Ramstack.HtmxToolkit/Configuration/HtmxV1Config.cs index 6c83cd9..b9bb20c 100644 --- a/src/Ramstack.HtmxToolkit/Configuration/HtmxV1Config.cs +++ b/src/Ramstack.HtmxToolkit/Configuration/HtmxV1Config.cs @@ -205,7 +205,7 @@ public HtmxBinaryType? WsBinaryType /// /// Gets or sets the selector for elements that HTMX must not process. - /// The HTMX default is [disable-htmx], [data-disable-htmx]. + /// The HTMX default is [hx-disable], [data-hx-disable]. /// public string? DisableSelector { @@ -235,7 +235,7 @@ public int? Timeout /// /// Gets or sets a value indicating whether requests are restricted to the current origin. - /// The HTMX default is . + /// The HTMX default is . /// public bool? SelfRequestsOnly { diff --git a/src/Ramstack.HtmxToolkit/Configuration/HtmxV2Config.cs b/src/Ramstack.HtmxToolkit/Configuration/HtmxV2Config.cs index 0b6b95a..c6df3b6 100644 --- a/src/Ramstack.HtmxToolkit/Configuration/HtmxV2Config.cs +++ b/src/Ramstack.HtmxToolkit/Configuration/HtmxV2Config.cs @@ -205,7 +205,7 @@ public HtmxBinaryType? WsBinaryType /// /// Gets or sets the selector for elements that HTMX must not process. - /// The HTMX default is [disable-htmx], [data-disable-htmx]. + /// The HTMX default is [hx-disable], [data-hx-disable]. /// public string? DisableSelector { diff --git a/src/Ramstack.HtmxToolkit/Configuration/ResponseHandlingConfig.cs b/src/Ramstack.HtmxToolkit/Configuration/ResponseHandlingConfig.cs index 7e3ee82..ffb187e 100644 --- a/src/Ramstack.HtmxToolkit/Configuration/ResponseHandlingConfig.cs +++ b/src/Ramstack.HtmxToolkit/Configuration/ResponseHandlingConfig.cs @@ -27,7 +27,7 @@ public sealed class ResponseHandlingConfig public bool? IgnoreTitle { get; set; } /// - /// Gets or sets a CSS selector to use to select content from the response. + /// Gets or sets a CSS selector used to select content from the response. /// public string? Select { get; set; } diff --git a/src/Ramstack.HtmxToolkit/HtmxLocationOptions.cs b/src/Ramstack.HtmxToolkit/HtmxLocationOptions.cs index 713130b..e585871 100644 --- a/src/Ramstack.HtmxToolkit/HtmxLocationOptions.cs +++ b/src/Ramstack.HtmxToolkit/HtmxLocationOptions.cs @@ -56,7 +56,7 @@ public sealed class HtmxLocationOptions /// /// Gets or sets a selector used to select content for out-of-band swaps from the response. /// - /// Supported only in HTMX 2.x. + /// Supported in HTMX 2.x and HTMX 4.x. [JsonPropertyName("selectOOB")] public string? SelectOob { get; set; } diff --git a/src/Ramstack.HtmxToolkit/HtmxResponse.cs b/src/Ramstack.HtmxToolkit/HtmxResponse.cs index 5f8f8d6..31c8914 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponse.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponse.cs @@ -77,7 +77,7 @@ static HtmxResponse LocationImpl(HtmxResponse response, string path, HtmxLocatio /// The possible values for this header are: /// /// - /// A URL to be pushed into the location bar. This may be relative or absolute, + /// A relative or same-origin absolute URL to be pushed into the location bar, /// as supported by history.pushState(). /// /// diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs index e23cd69..e35919e 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs @@ -53,7 +53,7 @@ public int? Timeout /// /// /// - /// In HTMX 1.x and 2.x, this property maps to the Boolean + /// In HTMX 1.x and 2.x, this property maps to the boolean /// credentials option of hx-request. /// yields . /// yields . @@ -206,7 +206,7 @@ internal readonly struct HtmxRequestDataPrior(HtmxRequestData data) { HtmxRequestCredentials.SameOrigin => false, HtmxRequestCredentials.Include => true, - // TODO: Consider throwing an exception when an HTMX 4-only credentials mode is configured for HTMX 1.x or 2.x + // TODO: Consider throwing an exception when an HTMX 4-only credentials mode is configured for HTMX 1.x or 2.x. // HtmxRequestCredentials.Omit => throw new InvalidOperationException(), _ => null }; diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxUrlTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxUrlTagHelper.cs index a0a1d5f..eea2798 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxUrlTagHelper.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxUrlTagHelper.cs @@ -162,7 +162,7 @@ public override Task ProcessAsync(TagHelperContext context, TagHelperOutput outp if (Area is not null) { - // Unconditionally replace any value from hx-area + // Unconditionally replace any existing area route value with the value from hx-area. routeValues ??= new RouteValueDictionary(); routeValues["area"] = Area; } diff --git a/tests/Ramstack.HtmxToolkit.Tests/JsonHelper.cs b/tests/Ramstack.HtmxToolkit.Tests/JsonHelper.cs index c122c49..55daa08 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/JsonHelper.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/JsonHelper.cs @@ -8,11 +8,11 @@ namespace Ramstack.HtmxToolkit.Tests; public static class JsonHelper { /// - /// Deserializes a JSON object into a dictionary for key/value assertions. + /// Deserializes a JSON object into a dictionary for key-value assertions. /// /// The JSON object to deserialize. /// - /// The deserialized key/value pairs. + /// The deserialized key-value pairs. /// public static Dictionary ParseJson(string json) => JsonSerializer.Deserialize>(json)!; diff --git a/tests/Ramstack.HtmxToolkit.Tests/TestHelper.cs b/tests/Ramstack.HtmxToolkit.Tests/TestHelper.cs index 5f3fe0c..88628a1 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/TestHelper.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/TestHelper.cs @@ -13,9 +13,9 @@ namespace Ramstack.HtmxToolkit.Tests; internal static class TestHelper { /// - /// Creates a with the given request headers. + /// Creates a with the specified request headers. /// - /// The request headers as key/value pairs. + /// The request headers as key-value pairs. /// /// The configured . /// @@ -30,7 +30,7 @@ public static HttpContext CreateHttpContext(params (string Name, string Value)[] } /// - /// Creates a whose request is an htmx request. + /// Creates a whose request is an HTMX request. /// /// to mark the request as boosted; /// otherwise, . @@ -49,7 +49,7 @@ public static HttpContext CreateHtmxRequestContext(bool boosted = false) } /// - /// Creates a whose request is an htmx request + /// Creates a whose request is an HTMX request /// and whose services target the specified HTMX major version. /// /// The HTMX major version to configure. @@ -106,7 +106,7 @@ private static HttpContext CreateContext(HtmxTargetVersion version) } /// - /// Creates a with the given tag name, attributes and items. + /// Creates a with the specified tag name, attributes, and items. /// public static TagHelperContext CreateTagHelperContext(string tagName = "div", TagHelperAttributeList? attributes = null, IDictionary? items = null) => new(tagName, attributes ?? [], items ?? new Dictionary(), "test"); From 9c386756826e79eae2d6010d59b7fce151342958 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 4 Sep 2026 21:49:56 +0500 Subject: [PATCH 2/4] Correct comments --- .editorconfig | 2 +- .gitignore | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8c749ed..a9e5d7e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -74,7 +74,7 @@ dotnet_diagnostic.CS8653.severity = error # CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. dotnet_diagnostic.CS8714.severity = error -# IDE0059: Unnecessary assigment of a value +# IDE0059: Unnecessary assignment of a value dotnet_diagnostic.IDE0059.severity = none # IDE0060: Remove unused parameter diff --git a/.gitignore b/.gitignore index 0f8b1b5..e6527d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -#Ignore thumbnails created by windows +#Ignore thumbnails created by Windows [Tt]humbs.db -#Ignore files build by Visual Studio +#Ignore files built by Visual Studio *.obj *.exe *.pdb @@ -33,7 +33,7 @@ obj/ *.ReSharper.user [Tt]est[Rr]esult* -#Ignore Rider/Idea files +#Ignore JetBrains IDE files .idea/ #Ignore npm modules From a723bd3ed0bdabff84be5008fbf1bd70f7b6ec7a Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 4 Sep 2026 22:28:16 +0500 Subject: [PATCH 3/4] Clarify HTMX request-header version support --- README.md | 3 ++- .../HtmxRequestHeaderNames.cs | 16 +++++++++++- .../HtmxRequestHeaders.cs | 26 ++++++++++++++----- src/Ramstack.HtmxToolkit/HtmxResponse.cs | 22 ++++++++-------- .../HtmxResponseHeaders.cs | 11 +++++--- src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs | 10 ++++--- 6 files changed, 60 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 256a668..75d4e42 100644 --- a/README.md +++ b/README.md @@ -412,7 +412,8 @@ app.MapHtmxToolkitScript("/assets/htmx-toolkit.js"); > [!IMPORTANT] > HTMX 1.9.x and 2.x support `HX-Trigger`, `HX-Trigger-After-Swap`, and `HX-Trigger-After-Settle`. -> HTMX 4.x supports only `HX-Trigger`. HtmxToolkit therefore emits events requested for any `HtmxTriggerTiming` +> HTMX 4.x supports only `HX-Trigger`, which fires when the request completes (after the swap whenever one is performed). +> HtmxToolkit therefore emits events requested for any `HtmxTriggerTiming` > value through that header rather than dropping them. The `Receive` and `AfterSettle` timings cannot be preserved exactly. ### Polling diff --git a/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs b/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs index f553f07..1760a4f 100644 --- a/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs +++ b/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs @@ -28,6 +28,10 @@ public static class HtmxRequestHeaderNames /// /// The HX-Prompt header contains the user's response to an hx-prompt. /// + /// + /// Supported only in HTMX 1.x and 2.x. HTMX 4.x removed hx-prompt + /// and does not send this header. + /// public const string Prompt = "HX-Prompt"; /// @@ -37,17 +41,27 @@ public static class HtmxRequestHeaderNames public const string Request = "HX-Request"; /// - /// The HX-Target header contains the ID of the target element, if present. + /// The HX-Target header identifies the target element, if present. /// + /// + /// In HTMX 1.x and 2.x, the value is the ID of the target element. + /// In HTMX 4.x, the value is in tag#id format, for example div#results. + /// public const string Target = "HX-Target"; /// /// The HX-Trigger-Name header contains the name of the triggered element, if present. /// + /// + /// Supported only in HTMX 1.x and 2.x. HTMX 4.x identifies the source element with HX-Source instead. + /// public const string TriggerName = "HX-Trigger-Name"; /// /// The HX-Trigger header contains the ID of the triggered element, if present. /// + /// + /// Supported only in HTMX 1.x and 2.x. HTMX 4.x identifies the source element with HX-Source instead. + /// public const string Trigger = "HX-Trigger"; } diff --git a/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs b/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs index 550ff25..3b0c114 100644 --- a/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs +++ b/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs @@ -51,7 +51,11 @@ internal HtmxRequestHeaders(HttpRequest request) => /// Gets the user's response to an hx-prompt on the client. /// /// - /// The header name is . + /// The header name is . + /// + /// Supported only in HTMX 1.x and 2.x; HTMX 4.x removed hx-prompt + /// and does not send this header. + /// /// public string? Prompt => GetString(_headers, HtmxRequestHeaderNames.Prompt); @@ -64,10 +68,12 @@ internal HtmxRequestHeaders(HttpRequest request) => public bool Request => GetBoolean(_headers, HtmxRequestHeaderNames.Request); /// - /// Gets the ID of the target element, if present. + /// Gets the identifier of the target element, if present. /// /// - /// The header name is . + /// The header name is . + /// In HTMX 1.x and 2.x, the value is the ID of the target element. + /// In HTMX 4.x, the value is in tag#id format, for example div#results. /// public string? Target => GetString(_headers, HtmxRequestHeaderNames.Target); @@ -75,7 +81,11 @@ internal HtmxRequestHeaders(HttpRequest request) => /// Gets the name of the triggered element, if present. /// /// - /// The header name is . + /// The header name is . + /// + /// Supported only in HTMX 1.x and 2.x; HTMX 4.x identifies the source element + /// with HX-Source instead. + /// /// public string? TriggerName => GetString(_headers, HtmxRequestHeaderNames.TriggerName); @@ -83,7 +93,10 @@ internal HtmxRequestHeaders(HttpRequest request) => /// Gets the ID of the triggered element, if present. /// /// - /// The header name is . + /// The header name is . + /// + /// Supported only in HTMX 1.x and 2.x; HTMX 4.x identifies the source element with HX-Source instead. + /// /// public string? Trigger => GetString(_headers, HtmxRequestHeaderNames.Trigger); @@ -93,8 +106,7 @@ internal HtmxRequestHeaders(HttpRequest request) => /// The header collection to inspect. /// The name of the header. /// - /// if the header value is "true"; - /// otherwise, . + /// if the header value is "true"; otherwise, . /// private static bool GetBoolean(IHeaderDictionary dictionary, string key) => dictionary.TryGetValue(key, out var value) && value[0] == "true"; diff --git a/src/Ramstack.HtmxToolkit/HtmxResponse.cs b/src/Ramstack.HtmxToolkit/HtmxResponse.cs index 31c8914..ecccc23 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponse.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponse.cs @@ -202,7 +202,7 @@ public HtmxResponse Reselect(string value) => /// /// /// In HTMX 4.x, every value is emitted through - /// HX-Trigger and runs after the swap. + /// HX-Trigger and runs when the request completes (after the swap whenever one is performed). /// See PR #3900. /// public HtmxResponse TriggerEvent(string eventName, HtmxTriggerTiming trigger = HtmxTriggerTiming.Receive) => @@ -212,17 +212,17 @@ public HtmxResponse TriggerEvent(string eventName, HtmxTriggerTiming trigger = H /// Adds a client-side event and its detail to the response header selected by /// . /// + /// + /// In HTMX 4.x, every value is emitted through HX-Trigger + /// and runs when the request completes (after the swap whenever one is performed). + /// See PR #3900. + /// /// The event name to trigger. /// The event detail. /// The event timing. Defaults to . /// /// The current instance. /// - /// - /// In HTMX 4.x, every value is emitted through - /// HX-Trigger and runs after the swap. - /// See PR #3900. - /// public HtmxResponse TriggerEvent(string eventName, object detail, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive) { return TriggerEventImpl(this, eventName, detail, timing); @@ -234,16 +234,16 @@ static HtmxResponse TriggerEventImpl(HtmxResponse response, string eventName, ob /// /// Adds client-side events to the response header selected by . /// + /// + /// In HTMX 4.x, every value is emitted through HX-Trigger + /// and runs when the request completes (after the swap whenever one is performed). + /// See PR #3900. + /// /// The event names and their associated details. /// The event timing. Defaults to . /// /// The current instance. /// - /// - /// In HTMX 4.x, every value is emitted through - /// HX-Trigger and runs after the swap. - /// See PR #3900. - /// public HtmxResponse TriggerEvents(IReadOnlyDictionary events, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive) => AddEvents(this, events, timing); diff --git a/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs b/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs index f2ca589..07baeb0 100644 --- a/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs +++ b/src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs @@ -126,7 +126,8 @@ public string Reselect /// /// /// HTMX 1.x and 2.x trigger these events when the response is received, - /// whereas HTMX 4.x triggers them after the swap. + /// whereas HTMX 4.x triggers them when the request completes + /// (after the swap whenever one is performed). /// /// [MaybeNull] @@ -147,7 +148,8 @@ public IReadOnlyDictionary Trigger /// /// /// In HTMX 4.x, assigned events are accumulated in - /// and emitted through HX-Trigger after the swap. + /// and emitted through HX-Trigger when the request completes + /// (after the swap whenever one is performed). /// /// [MaybeNull] @@ -168,8 +170,9 @@ public IReadOnlyDictionary TriggerAfterSwap /// /// /// In HTMX 4.x, assigned events are accumulated in - /// and emitted through HX-Trigger after the swap; the requested - /// after-settle timing cannot be preserved. + /// and emitted through HX-Trigger when the request completes + /// (after the swap whenever one is performed); the requested after-settle timing + /// cannot be preserved. /// /// [MaybeNull] diff --git a/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs b/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs index df63f21..80de07a 100644 --- a/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs +++ b/src/Ramstack.HtmxToolkit/HtmxTriggerTiming.cs @@ -11,7 +11,8 @@ public enum HtmxTriggerTiming /// /// HTMX 1.x and 2.x trigger these events when the response is received. /// - /// HTMX 4.x triggers them after the swap completes. + /// HTMX 4.x triggers them when the request completes, which is after the swap + /// whenever one is performed. /// See PR #3900. /// /// @@ -23,7 +24,8 @@ public enum HtmxTriggerTiming /// /// HTMX 1.x and 2.x emit these events through HX-Trigger-After-Swap. /// - /// HTMX 4.x emits them through HX-Trigger, which also runs after the swap. + /// HTMX 4.x emits them through HX-Trigger, which also fires when the request + /// completes (after the swap whenever one is performed). /// See PR #3900. /// /// @@ -36,8 +38,8 @@ public enum HtmxTriggerTiming /// /// HTMX 1.x and 2.x emit these events through HX-Trigger-After-Settle. /// - /// HTMX 4.x emits them through HX-Trigger after the swap; the requested - /// after-settle timing cannot be preserved. + /// 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. /// See PR #3900. /// /// From 64bc904c7b7b7dd914e2a1949cb4ed9a6beaa162 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 4 Sep 2026 22:32:03 +0500 Subject: [PATCH 4/4] Add HTMX 4.x Source and RequestType request headers --- README.md | 10 ++++++++ .../HtmxRequestHeaderNames.cs | 18 +++++++++++++++ .../HtmxRequestHeaders.cs | 23 +++++++++++++++++++ .../HtmxRequestHeadersTests.cs | 16 +++++++++++++ 4 files changed, 67 insertions(+) diff --git a/README.md b/README.md index 75d4e42..2f65336 100644 --- a/README.md +++ b/README.md @@ -123,10 +123,20 @@ Available properties include: - `HistoryRestoreRequest` - `Prompt` - `Request` +- `RequestType` +- `Source` - `Target` - `Trigger` - `TriggerName` +> [!NOTE] +> The request header vocabulary differs between HTMX versions. +> `HX-Trigger`, `HX-Trigger-Name`, and `HX-Prompt` are sent by HTMX 1.x and 2.x only. +> HTMX 4.x does not support `hx-prompt`, identifies the source element with `HX-Source` instead, +> and reports partial or full requests through `HX-Request-Type`. +> In HTMX 4.x, `HX-Target` also carries a `tag#id` value (for example, `div#results`) +> rather than an ID alone. + `HtmxRequestHeaderNames` exposes the corresponding header-name constants for lower-level APIs. Use `Request.IsHtmxBoosted()` when only boosted navigation matters. An overload also provides access to the strongly typed headers. diff --git a/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs b/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs index 1760a4f..c92dccd 100644 --- a/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs +++ b/src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs @@ -40,6 +40,24 @@ public static class HtmxRequestHeaderNames /// public const string Request = "HX-Request"; + /// + /// The HX-Request-Type header indicates whether the request expects a partial + /// or a full page response. + /// + /// + /// Supported only in HTMX 4.x. The value is "partial" for targeted swaps + /// and "full" for body-level or hx-select requests. + /// + public const string RequestType = "HX-Request-Type"; + + /// + /// The HX-Source header identifies the element that triggered the request. + /// + /// + /// Supported only in HTMX 4.x. The value is in tag#id format, for example button#submit. + /// + public const string Source = "HX-Source"; + /// /// The HX-Target header identifies the target element, if present. /// diff --git a/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs b/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs index 3b0c114..69b7d92 100644 --- a/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs +++ b/src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs @@ -67,6 +67,29 @@ internal HtmxRequestHeaders(HttpRequest request) => /// public bool Request => GetBoolean(_headers, HtmxRequestHeaderNames.Request); + /// + /// Gets the type of the expected response, either a partial or a full page. + /// + /// + /// The header name is . + /// + /// Supported only in HTMX 4.x. The value is "partial" for targeted swaps + /// and "full" for body-level or hx-select requests. + /// + /// + public string? RequestType => GetString(_headers, HtmxRequestHeaderNames.RequestType); + + /// + /// Gets the identifier of the element that triggered the request. + /// + /// + /// The header name is . + /// + /// Supported only in HTMX 4.x. The value is in tag#id format, for example button#submit. + /// + /// + public string? Source => GetString(_headers, HtmxRequestHeaderNames.Source); + /// /// Gets the identifier of the target element, if present. /// diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestHeadersTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestHeadersTests.cs index f0a0f2d..7cbf0b8 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestHeadersTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestHeadersTests.cs @@ -52,6 +52,20 @@ public void Request_ReturnsHeaderValue() Assert.That(headers.Request, Is.True); } + [Test] + public void RequestType_ReturnsHeaderValue() + { + var headers = CreateHeaders((HtmxRequestHeaderNames.RequestType, "partial")); + Assert.That(headers.RequestType, Is.EqualTo("partial")); + } + + [Test] + public void Source_ReturnsHeaderValue() + { + var headers = CreateHeaders((HtmxRequestHeaderNames.Source, "button#submit")); + Assert.That(headers.Source, Is.EqualTo("button#submit")); + } + [Test] public void Target_ReturnsHeaderValue() { @@ -80,6 +94,8 @@ public void StringProperties_ReturnNull_WhenHeaderAbsent() Assert.That(headers.CurrentUrl, Is.Null); Assert.That(headers.Prompt, Is.Null); + Assert.That(headers.RequestType, Is.Null); + Assert.That(headers.Source, Is.Null); Assert.That(headers.Target, Is.Null); Assert.That(headers.TriggerName, Is.Null); Assert.That(headers.Trigger, Is.Null);