From 19a81547663be13c667878c5141b88b06f9137d1 Mon Sep 17 00:00:00 2001 From: rameel Date: Mon, 10 Aug 2026 16:59:56 +0500 Subject: [PATCH] Add support for HTMX 2.x --- README.md | 57 ++++++++ .../HtmxScrollBehavior.cs | 8 +- .../Internal/EnumHelper.cs | 33 +++-- .../HtmxConfigJsonSerializerContext.cs | 3 +- .../TagHelpers/HtmxConfigTagHelper.cs | 137 ++++++++++++++++-- .../TagHelpers/ResponseHandlingEntry.cs | 43 ++++++ .../ResponseHandlingEntryTagHelper.cs | 81 +++++++++++ 7 files changed, 337 insertions(+), 25 deletions(-) create mode 100644 src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntry.cs create mode 100644 src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntryTagHelper.cs diff --git a/README.md b/README.md index b0934ba..efba93c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Provides HTMX integration for ASP.NET Core applications. +* [HtmxToolkit](#htmxtoolkit) * [Getting Started](#getting-started) * [HttpRequest](#httprequest) * [HtmxRequestAttribute](#htmxrequestattribute) @@ -14,6 +15,7 @@ Provides HTMX integration for ASP.NET Core applications. * [HtmxUrlTagHelper](#htmxurltaghelper) * [HtmxHeaderTagHelper](#htmxheadertaghelper) * [HtmxConfigTagHelper](#htmxconfigtaghelper) + * [Response Handling Configuration](#response-handling-configuration) * [Antiforgery Token](#antiforgery-token) * [Supported Versions](#supported-versions) * [Contributions](#contributions) @@ -618,6 +620,61 @@ If desired or for the purpose of semantics, you can use `htmx-config` as the sta include-antiforgery-token="true" /> ``` +#### Response Handling Configuration + +HTMX 2.x introduces the [`responseHandling`](https://htmx.org/docs/#response-handling) configuration option, +allowing you to define how htmx should handle responses based on HTTP status codes. +The library provides a child tag helper `` that can be placed inside `` +to declaratively configure response handling rules. + +```html + + + + + + + + + + + + + + + + +``` + +The following code will be generated: + +```html + +``` + +The `` element supports the following attributes: + +| Attribute | Type | Description | +|-----------------|----------|------------------------------------------------------------------| +| `code` | `string` | Regular expression tested against response status codes | +| `swap` | `bool?` | Whether the response should be swapped into the DOM | +| `error` | `bool?` | Whether htmx should treat this response as an error | +| `ignore-title` | `bool?` | Whether to ignore title tags in the response | +| `select` | `string` | CSS selector to select content from the response | +| `target` | `string` | CSS selector specifying an alternative target for the response | +| `swap-override` | `string` | Alternative swap mechanism for the response | + +Alternatively, you can set the entire response handling configuration directly as a Razor expression: + +```html + +``` + ## Antiforgery Token If you have enabled the generation of the **Antiforgery** token in the configuration diff --git a/src/Ramstack.HtmxToolkit/HtmxScrollBehavior.cs b/src/Ramstack.HtmxToolkit/HtmxScrollBehavior.cs index 932cffc..2182ae1 100644 --- a/src/Ramstack.HtmxToolkit/HtmxScrollBehavior.cs +++ b/src/Ramstack.HtmxToolkit/HtmxScrollBehavior.cs @@ -13,5 +13,11 @@ public enum HtmxScrollBehavior /// /// Specifies smooth scrolling to the top of the page. /// - Smooth + Smooth, + + /// + /// Specifies instant scrolling with no animation. + /// Supported only in HTMX 2.x. + /// + Instant } diff --git a/src/Ramstack.HtmxToolkit/Internal/EnumHelper.cs b/src/Ramstack.HtmxToolkit/Internal/EnumHelper.cs index e4cb73c..10de98e 100644 --- a/src/Ramstack.HtmxToolkit/Internal/EnumHelper.cs +++ b/src/Ramstack.HtmxToolkit/Internal/EnumHelper.cs @@ -20,10 +20,17 @@ public static string GetWsBinaryTypeValue(this HtmxBinaryType value) => /// /// The value. /// - /// The string representation: either "auto" or "smooth". + /// The string representation: "auto", "smooth" or "instant". /// - public static string GetScrollBehaviorValue(this HtmxScrollBehavior value) => - value == HtmxScrollBehavior.Auto ? "auto" : "smooth"; + public static string GetScrollBehaviorValue(this HtmxScrollBehavior value) + { + return value switch + { + HtmxScrollBehavior.Auto => "auto", + HtmxScrollBehavior.Smooth => "smooth", + _ => "instant" + }; + } /// /// Converts a value to its corresponding string representation, @@ -47,17 +54,17 @@ public static string GetScrollBehaviorValue(this HtmxScrollBehavior value) => /// public static string GetSwapValue(this HtmxSwap value) { - switch (value) + return value switch { - case HtmxSwap.InnerHtml: return "innerHTML"; - case HtmxSwap.OuterHtml: return "outerHTML"; - case HtmxSwap.BeforeBegin: return "beforebegin"; - case HtmxSwap.AfterBegin: return "afterbegin"; - case HtmxSwap.BeforeEnd: return "beforeend"; - case HtmxSwap.AfterEnd: return "afterend"; - case HtmxSwap.Delete: return "delete"; - default: return "none"; - } + HtmxSwap.InnerHtml => "innerHTML", + HtmxSwap.OuterHtml => "outerHTML", + HtmxSwap.BeforeBegin => "beforebegin", + HtmxSwap.AfterBegin => "afterbegin", + HtmxSwap.BeforeEnd => "beforeend", + HtmxSwap.AfterEnd => "afterend", + HtmxSwap.Delete => "delete", + _ => "none" + }; } /// diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigJsonSerializerContext.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigJsonSerializerContext.cs index ef77bad..b1d7520 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigJsonSerializerContext.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigJsonSerializerContext.cs @@ -9,6 +9,7 @@ namespace Ramstack.HtmxToolkit.TagHelpers; DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(HtmxConfigTagHelper.HtmxConfiguration))] -[JsonSerializable(typeof(HtmxConfigTagHelper.AntiForgeryTokenData))] +[JsonSerializable(typeof(HtmxConfigTagHelper.AntiForgeryTokenData?))] +[JsonSerializable(typeof(IList))] internal partial class HtmxConfigJsonSerializerContext : JsonSerializerContext; #endif diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs index 6315fed..5d56999 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxConfigTagHelper.cs @@ -16,22 +16,31 @@ namespace Ramstack.HtmxToolkit.TagHelpers; /// to declaratively define htmx options. /// [HtmlTargetElement("meta", Attributes = "htmx-config", TagStructure = TagStructure.WithoutEndTag)] -[HtmlTargetElement("htmx-config", TagStructure = TagStructure.WithoutEndTag)] +[HtmlTargetElement("htmx-config", TagStructure = TagStructure.NormalOrSelfClosing)] public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper { private readonly IAntiforgery _antiforgery = antiforgery; + /// + /// The key used to pass items between + /// and this tag helper via . + /// + internal const string ResponseHandlingEntriesKey = "Htmx_ResponseHandlingEntries"; + /// /// Gets or sets a value indicating whether history is enabled. /// Defaults to . /// - /// This is mainly useful for testing. + /// + /// Supported in HTMX 1.x and 2.x. This is mainly useful for testing. + /// [HtmlAttributeName("history-enabled")] public bool? HistoryEnabled { get; set; } /// /// Gets or sets the size of the history cache. Defaults to 10. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("history-cache-size")] public int? HistoryCacheSize { get; set; } @@ -40,6 +49,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// should be issued on history misses rather than using an AJAX request. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("refresh-on-history-miss")] public bool? RefreshOnHistoryMiss { get; set; } @@ -47,18 +57,21 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets the default swap style. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("default-swap-style")] public HtmxSwap? DefaultSwapStyle { get; set; } /// /// Gets or sets the default swap delay. Defaults to 0. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("default-swap-delay")] public int? DefaultSwapDelay { get; set; } /// /// Gets or sets the default settle delay. Defaults to 20. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("default-settle-delay")] public int? DefaultSettleDelay { get; set; } @@ -66,36 +79,42 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value indicating whether the indicator styles are loaded. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("include-indicator-styles")] public bool? IncludeIndicatorStyles { get; set; } /// /// Gets or sets the indicator class. Defaults to htmx-indicator. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("indicator-class")] public string? IndicatorClass { get; set; } /// /// Gets or sets the request class. Defaults to htmx-request. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("request-class")] public string? RequestClass { get; set; } /// /// Gets or sets the added class. Defaults to htmx-added. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("added-class")] public string? AddedClass { get; set; } /// /// Gets or sets the swapping class. Defaults to htmx-swapping. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("swapping-class")] public string? SwappingClass { get; set; } /// /// Gets or sets the settling class. Defaults to htmx-settling. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("settling-class")] public string? SettlingClass { get; set; } @@ -103,6 +122,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value indicating whether eval is allowed. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("allow-eval")] public bool? AllowEval { get; set; } @@ -110,6 +130,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value indicating whether script tags should be processed in new content. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("allow-script-tags")] public bool? AllowScriptTags { get; set; } @@ -117,13 +138,23 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value meaning that no nonce will be added to inline scripts. /// Defaults to "". /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("inline-script-nonce")] public string? InlineScriptNonce { get; set; } + /// + /// Gets or sets a value meaning that no nonce will be added to inline styles. + /// Defaults to "". + /// + /// Supported only in HTMX 2.x. + [HtmlAttributeName("inline-style-nonce")] + public string? InlineStyleNonce { get; set; } + /// /// Gets or sets the attributes to settle during the settling phase. /// Defaults to ["class", "style", "width", "height"]. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("attributes-to-settle")] public string[]? AttributesToSettle { get; set; } @@ -131,12 +162,14 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value indicating whether HTML template tags should be used for parsing content. /// Defaults to . /// + /// Supported only in HTMX 1.x. Removed in HTMX 2.x. [HtmlAttributeName("use-template-fragments")] public bool? UseTemplateFragments { get; set; } /// /// Gets or sets the WebSocket reconnect delay. Defaults to full-jitter. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("ws-reconnect-delay")] public string? WsReconnectDelay { get; set; } @@ -144,6 +177,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets the type of binary data /// being received over the WebSocket connection. Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("ws-binary-type")] public HtmxBinaryType? WsBinaryType { get; set; } @@ -152,6 +186,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Defaults to [disable-htmx], [data-disable-htmx]. /// HTMX will not process elements with this attribute on it or a parent. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("disable-selector")] public string? DisableSelector { get; set; } @@ -160,20 +195,34 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// using credentials such as cookies, authorization headers or TLS client certificates. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("with-credentials")] public bool? WithCredentials { get; set; } + /// + /// Gets or sets a value indicating whether htmx attribute inheritance is disabled. + /// If set to , the inheritance of attributes is completely disabled + /// and you can explicitly specify the inheritance with the hx-inherit attribute. + /// Defaults to . + /// + /// Supported only in HTMX 2.x. + [HtmlAttributeName("disable-inheritance")] + public bool? DisableInheritance { get; set; } + /// /// Gets or sets the number of milliseconds a request can take before automatically being terminated. /// Defaults to 0. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("timeout")] public int? Timeout { get; set; } /// /// Gets or sets a value indicating the behavior for a boosted link on page transitions. - /// Defaults to . + /// Defaults to in HTMX 1.x + /// and in HTMX 2.x. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("scroll-behavior")] public HtmxScrollBehavior? ScrollBehavior { get; set; } @@ -181,6 +230,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value indicating whether the focused element should be scrolled into view. /// Defaults to and can be overridden using the focus-scroll swap modifier. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("default-focus-scroll")] public bool? DefaultFocusScroll { get; set; } @@ -189,6 +239,11 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// should be included in GET requests to avoid caching partial responses by the browser. /// Defaults to . /// + /// + /// Supported in HTMX 1.x and 2.x. + /// In HTMX 2.x, the format changed to org.htmx.cache-buster=targetElementId, + /// where the target element is appended to the GET request. + /// [HtmlAttributeName("get-cache-buster-param")] public bool? GetCacheBusterParam { get; set; } @@ -198,21 +253,30 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// should be used when swapping in new content. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("global-view-transitions")] public bool? GlobalViewTransitions { get; set; } /// /// Gets or sets a list of HTTP methods that use URL parameters. - /// Defaults to ["get"]. Allowed values: "get", "head", "post", "put", - /// "delete", "connect", "options", "trace", "patch". + /// Defaults to ["get"] in HTMX 1.x and ["get", "delete"] in HTMX 2.x. /// + /// + /// Supported in HTMX 1.x and 2.x. + /// + /// Allowed values: "get", "head", "post", "put", + /// "delete", "connect", "options", "trace", "patch". + /// + /// [HtmlAttributeName("methods-that-use-url-params")] public string[]? MethodsThatUseUrlParams { get; set; } /// /// Gets or sets a value indicating whether AJAX requests should be allowed only - /// to the same domain as the current document. Defaults to . + /// to the same domain as the current document. + /// Defaults to in HTMX 1.x and in HTMX 2.x. /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("self-requests-only")] public bool? SelfRequestsOnly { get; set; } @@ -220,6 +284,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// Gets or sets a value indicating whether htmx should not update the title of the document /// when a title tag is found in new content. Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("ignore-title")] public bool? IgnoreTitle { get; set; } @@ -229,6 +294,7 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// the target defaults to body, causing the page to scroll to the top. /// Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("scroll-into-view-on-boost")] public bool? ScrollIntoViewOnBoost { get; set; } @@ -238,13 +304,54 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper /// You may define a simple object to use a never-clearing cache or implement your own system /// using a proxy object. Defaults to . /// + /// Supported in HTMX 1.x and 2.x. [HtmlAttributeName("trigger-specs-cache")] public string? TriggerSpecsCache { get; set; } + /// + /// Gets or sets the default response handling behavior for HTTP response status codes. + /// Accepts an array of objects that define + /// how htmx should handle responses matching specific status code patterns. + /// + /// Supported only in HTMX 2.x. + [HtmlAttributeName("response-handling")] + public IList? ResponseHandling { get; set; } + + /// + /// Gets or sets a value indicating whether to process OOB swaps + /// on elements that are nested within the main response element. + /// Defaults to . + /// + /// Supported only in HTMX 2.x. + [HtmlAttributeName("allow-nested-oob-swaps")] + public bool? AllowNestedOobSwaps { get; set; } + + /// + /// Gets or sets a value indicating whether to treat history cache miss + /// full page reload requests as an "HX-Request" by returning the corresponding response header. + /// Defaults to . + /// This should always be disabled when using the HX-Request header + /// to optionally return partial responses. + /// + /// Supported only in HTMX 2.x. + [HtmlAttributeName("history-restore-as-hx-request")] + public bool? HistoryRestoreAsHxRequest { get; set; } + + /// + /// Gets or sets a value indicating whether to report input validation errors + /// to the end user and update focus to the first input that fails validation. + /// Defaults to . + /// This should always be enabled as this matches default browser form submit behavior. + /// + /// Supported only in HTMX 2.x. + [HtmlAttributeName("report-validity-of-forms")] + public bool? ReportValidityOfForms { get; set; } + /// /// Gets or sets a value indicating whether an antiforgery token should be included. /// Defaults to . /// + /// This is a custom extension, not part of the htmx configuration. [HtmlAttributeName("include-antiforgery-token")] public bool IncludeAntiForgeryToken { get; set; } @@ -256,15 +363,21 @@ public sealed class HtmxConfigTagHelper(IAntiforgery antiforgery) : TagHelper public ViewContext ViewContext { get; set; } = null!; /// - public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (output.TagName == "meta") output.Attributes.RemoveAll("htmx-config"); output.TagName = "meta"; - + output.TagMode = TagMode.SelfClosing; output.Attributes.SetAttribute("name", "htmx-config"); + await output.GetChildContentAsync(); + + if (context.Items.TryGetValue(ResponseHandlingEntriesKey, out var value)) + if (value is List entries && entries.Count != 0) + ResponseHandling = entries; + #if NET8_0_OR_GREATER var config = new HtmlString( JsonSerializer.Serialize( @@ -279,8 +392,6 @@ public override Task ProcessAsync(TagHelperContext context, TagHelperOutput outp output.Attributes.SetAttribute( new TagHelperAttribute("content", config, HtmlAttributeValueStyle.SingleQuotes)); - - return Task.CompletedTask; } #region Inner type: HtmxConfiguration @@ -306,12 +417,14 @@ internal readonly struct HtmxConfiguration(HtmxConfigTagHelper helper) public bool? AllowEval => helper.AllowEval; public bool? AllowScriptTags => helper.AllowScriptTags; public string? InlineScriptNonce => helper.InlineScriptNonce; + public string? InlineStyleNonce => helper.InlineStyleNonce; public string[]? AttributesToSettle => helper.AttributesToSettle; public bool? UseTemplateFragments => helper.UseTemplateFragments; public string? WsReconnectDelay => helper.WsReconnectDelay; public string? WsBinaryType => helper.WsBinaryType?.GetWsBinaryTypeValue(); public string? DisableSelector => helper.DisableSelector; public bool? WithCredentials => helper.WithCredentials; + public bool? DisableInheritance => helper.DisableInheritance; public int? Timeout => helper.Timeout; public string? ScrollBehavior => helper.ScrollBehavior?.GetScrollBehaviorValue(); public bool? DefaultFocusScroll => helper.DefaultFocusScroll; @@ -322,6 +435,10 @@ internal readonly struct HtmxConfiguration(HtmxConfigTagHelper helper) public bool? IgnoreTitle => helper.IgnoreTitle; public bool? ScrollIntoViewOnBoost => helper.ScrollIntoViewOnBoost; public string? TriggerSpecsCache => helper.TriggerSpecsCache; + public IList? ResponseHandling => helper.ResponseHandling; + public bool? AllowNestedOobSwaps => helper.AllowNestedOobSwaps; + public bool? HistoryRestoreAsHxRequest => helper.HistoryRestoreAsHxRequest; + public bool? ReportValidityOfForms => helper.ReportValidityOfForms; public AntiForgeryTokenData? AntiForgery => GetAntiForgeryToken(helper); private static AntiForgeryTokenData? GetAntiForgeryToken(HtmxConfigTagHelper h) => diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntry.cs b/src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntry.cs new file mode 100644 index 0000000..193f16d --- /dev/null +++ b/src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntry.cs @@ -0,0 +1,43 @@ +namespace Ramstack.HtmxToolkit.TagHelpers; + +/// +/// Represents a single entry in the htmx response handling configuration. +/// Each entry defines how htmx should handle responses matching a specific HTTP status code pattern. +/// +public sealed class ResponseHandlingEntry +{ + /// + /// Gets or sets a regular expression that will be tested against response status codes. + /// + public string? Code { get; set; } + + /// + /// Gets or sets a value indicating whether the response should be swapped into the DOM. + /// + public bool? Swap { get; set; } + + /// + /// Gets or sets a value indicating whether htmx should treat this response as an error. + /// + public bool? Error { get; set; } + + /// + /// Gets or sets a value indicating whether htmx should ignore title tags in the response. + /// + public bool? IgnoreTitle { get; set; } + + /// + /// Gets or sets a CSS selector to use to select content from the response. + /// + public string? Select { get; set; } + + /// + /// Gets or sets a CSS selector specifying an alternative target for the response. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an alternative swap mechanism for the response. + /// + public string? SwapOverride { get; set; } +} diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntryTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntryTagHelper.cs new file mode 100644 index 0000000..f767981 --- /dev/null +++ b/src/Ramstack.HtmxToolkit/TagHelpers/ResponseHandlingEntryTagHelper.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Ramstack.HtmxToolkit.TagHelpers; + +/// +/// Represents a that defines a single response handling entry +/// as a child of the htmx-config tag helper. +/// Each entry specifies how htmx should handle responses matching a particular HTTP status code pattern. +/// +/// Supported only in HTMX 2.x. +[HtmlTargetElement("response-handling", ParentTag = "htmx-config", TagStructure = TagStructure.WithoutEndTag)] +public sealed class ResponseHandlingEntryTagHelper : TagHelper +{ + /// + /// Gets or sets a regular expression that will be tested against response status codes. + /// + [HtmlAttributeName("code")] + public string? Code { get; set; } + + /// + /// Gets or sets a value indicating whether the response should be swapped into the DOM. + /// + [HtmlAttributeName("swap")] + public bool? Swap { get; set; } + + /// + /// Gets or sets a value indicating whether htmx should treat this response as an error. + /// + [HtmlAttributeName("error")] + public bool? Error { get; set; } + + /// + /// Gets or sets a value indicating whether htmx should ignore title tags in the response. + /// + [HtmlAttributeName("ignore-title")] + public bool? IgnoreTitle { get; set; } + + /// + /// Gets or sets a CSS selector to use to select content from the response. + /// + [HtmlAttributeName("select")] + public string? Select { get; set; } + + /// + /// Gets or sets a CSS selector specifying an alternative target for the response. + /// + [HtmlAttributeName("target")] + public string? Target { get; set; } + + /// + /// Gets or sets an alternative swap mechanism for the response. + /// + [HtmlAttributeName("swap-override")] + public string? SwapOverride { get; set; } + + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (!context.Items.TryGetValue(HtmxConfigTagHelper.ResponseHandlingEntriesKey, out var value)) + { + value = new List(); + context.Items[HtmxConfigTagHelper.ResponseHandlingEntriesKey] = value; + } + + if (value is List entries) + { + entries.Add(new ResponseHandlingEntry + { + Code = Code, + Swap = Swap, + Error = Error, + IgnoreTitle = IgnoreTitle, + Select = Select, + Target = Target, + SwapOverride = SwapOverride + }); + } + + output.SuppressOutput(); + } +}