diff --git a/src/Htmxor/Http/HtmxResponse.cs b/src/Htmxor/Http/HtmxResponse.cs
index 8c391aa..74d06ee 100644
--- a/src/Htmxor/Http/HtmxResponse.cs
+++ b/src/Htmxor/Http/HtmxResponse.cs
@@ -137,12 +137,25 @@ public HtmxResponse ReplaceUrl(string url)
return this;
}
+ ///
+ /// Allows you to specify how the response will be swapped.
+ ///
+ /// The hx-swap attributes supports modifiers for changing the behavior of the swap.
+ /// This object instance.
+ public HtmxResponse Reswap(string modifier)
+ {
+ headers[HtmxResponseHeaderNames.Reswap] = modifier;
+
+ return this;
+ }
+
///
/// Allows you to specify how the response will be swapped.
///
///
+ /// The hx-swap attributes supports modifiers for changing the behavior of the swap.
/// This object instance.
- public HtmxResponse Reswap(SwapStyle swapStyle)
+ public HtmxResponse Reswap(SwapStyle swapStyle, string? modifier = null)
{
AssertIsHtmxRequest();
@@ -159,11 +172,26 @@ public HtmxResponse Reswap(SwapStyle swapStyle)
_ => throw new SwitchExpressionException(swapStyle),
};
- headers[HtmxResponseHeaderNames.Reswap] = style;
+ var value = modifier != null ? $"{style} {modifier}" : style;
+
+ headers[HtmxResponseHeaderNames.Reswap] = value;
return this;
}
+ ///
+ /// Allows you to specify how the response will be swapped.
+ ///
+ ///
+ ///
+ ///
+ public HtmxResponse Reswap(SwapStyleBuilder swapStyle)
+ {
+ var (style, modifier) = swapStyle.Build();
+
+ return style is null ? Reswap(modifier) : Reswap((SwapStyle)style, modifier);
+ }
+
///
/// A CSS selector that updates the target of the content update to a different element on the page.
///
@@ -192,6 +220,17 @@ public HtmxResponse Reselect(string selector)
return this;
}
+ ///
+ /// Sets response code to stop polling
+ ///
+ ///
+ public HtmxResponse StopPolling()
+ {
+ context.Response.StatusCode = HtmxStatusCodes.StopPolling;
+
+ return this;
+ }
+
///
/// Allows you to trigger client-side events.
///
diff --git a/src/Htmxor/Http/HtmxStatusCodes.cs b/src/Htmxor/Http/HtmxStatusCodes.cs
new file mode 100644
index 0000000..06b2e78
--- /dev/null
+++ b/src/Htmxor/Http/HtmxStatusCodes.cs
@@ -0,0 +1,6 @@
+namespace Htmxor.Http;
+
+public static class HtmxStatusCodes
+{
+ public static readonly int StopPolling = 286;
+}
diff --git a/src/Htmxor/Http/SwapStyleBuilder.cs b/src/Htmxor/Http/SwapStyleBuilder.cs
new file mode 100644
index 0000000..433eb40
--- /dev/null
+++ b/src/Htmxor/Http/SwapStyleBuilder.cs
@@ -0,0 +1,591 @@
+using System.Collections;
+using System.Collections.Specialized;
+using System.Numerics;
+
+namespace Htmxor.Http;
+
+///
+/// A builder class for constructing a swap style command string for HTMX responses.
+///
+public sealed class SwapStyleBuilder
+{
+ private readonly SwapStyle? style;
+ private readonly OrderedDictionary modifiers = new();
+
+ ///
+ /// Initializes a new instance of the SwapStyleBuilder with a specified swap style.
+ ///
+ /// The initial swap style to be applied.
+ public SwapStyleBuilder(SwapStyle? style = null)
+ {
+ this.style = style;
+ }
+
+ ///
+ /// Modifies the amount of time that htmx will wait after receiving a
+ /// response to swap the content by including the modifier swap:.
+ ///
+ ///
+ /// will be converted to milliseconds if less than 1000, otherwise seconds,
+ /// meaning the resulting modifier will be swap:500ms for a of 500 milliseconds
+ /// or swap:2s for a of 2 seconds..
+ ///
+ /// The amount of time htmx should wait after receiving a
+ /// response to swap the content.
+ public SwapStyleBuilder AfterSwapDelay(TimeSpan time)
+ {
+ AddModifier("swap", time.TotalMilliseconds < 1000 ? $"{time.TotalMilliseconds}ms" : $"{time.TotalSeconds}s");
+
+ return this;
+ }
+
+ ///
+ /// Modifies the amount of time that htmx will wait between the swap
+ /// and the settle logic by including the modifier settle:.
+ ///
+ ///
+ /// will be converted to milliseconds if less than 1000, otherwise seconds,
+ /// meaning the resulting modifier will be settle:500ms for a of 500 milliseconds
+ /// or settle:2s for a of 2 seconds..
+ ///
+ /// The amount of time htmx should wait after receiving a
+ /// response to swap the content.
+ public SwapStyleBuilder AfterSettleDelay(TimeSpan time)
+ {
+ AddModifier("settle", time.TotalMilliseconds < 1000 ? $"{time.TotalMilliseconds}ms" : $"{time.TotalSeconds}s");
+
+ return this;
+ }
+
+ ///
+ /// Specifies how to set the content scrollbar position after the swap and appends the modifier scroll:.
+ ///
+ ///
+ /// Sets the swapped content scrollbar position after swapping immediately (without animation). For instance, using
+ /// will add the modifier scroll:top which sets the scrollbar position to the top of swap content after the swap.
+ /// If css is present then the page is scrolled to the of the content identified by the css selector.
+ ///
+ /// The scroll direction after the swap.
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder Scroll(ScrollDirection direction, string? selector = null)
+ {
+ switch (direction)
+ {
+ case ScrollDirection.Top:
+ AddModifier("scroll", selector is null ? "top" : $"{selector}:top");
+ break;
+ case ScrollDirection.Bottom:
+ AddModifier("scroll", selector is null ? "bottom" : $"{selector}:bottom");
+ break;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Sets the content scrollbar position to the top of the swapped content after a swap.
+ ///
+ ///
+ /// This method adds the modifier scroll:top to the swap commands, instructing the page to scroll to
+ /// the top of the content after content is swapped immediately and without animation. If css
+ /// is present then the page is scrolled to the top of the content identified by the css selector.
+ ///
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ScrollTop(string? selector = null) => Scroll(ScrollDirection.Top, selector);
+
+ ///
+ /// Sets the content scrollbar position to the bottom of the swapped content after a swap.
+ ///
+ ///
+ /// This method adds the modifier scroll:bottom to the swap commands, instructing the page to scroll to
+ /// the bottom of the content after content is swapped immediately and without animation. If css
+ /// is present then the page is scrolled to the bottom of the content identified by the css selector.
+ ///
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ScrollBottom(string? selector = null) => Scroll(ScrollDirection.Bottom, selector);
+
+ ///
+ /// Determines whether to ignore the document title in the swap response by appending the modifier
+ /// ignoreTitle:.
+ ///
+ ///
+ /// When set to true, the document title in the swap response will be ignored by adding the modifier
+ /// ignoreTitle:true.
+ /// This keeps the current title unchanged regardless of the incoming swap content's title tag.
+ ///
+ /// Whether to ignore the title.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder IgnoreTitle(bool ignore = true)
+ {
+ AddModifier("ignoreTitle", ignore);
+
+ return this;
+ }
+
+ ///
+ /// Includes the document title from the swap response in the current page.
+ ///
+ ///
+ /// This method ensures the title of the document is updated according to the swap response by removing any
+ /// ignoreTitle modifiers, effectively setting ignoreTitle:false.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder IncludeTitle() => IgnoreTitle(false);
+
+ ///
+ /// Enables or disables transition effects for the swap by appending the modifier transition:.
+ ///
+ ///
+ /// Controls the display of transition effects during the swap. For example, setting to true
+ /// will add the modifier transition:true to enable smooth transitions.
+ ///
+ /// Whether to show transition effects.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder Transition(bool show)
+ {
+ AddModifier("transition", show);
+
+ return this;
+ }
+
+ ///
+ /// Explicitly includes transition effects for the swap.
+ ///
+ ///
+ /// By calling this method, transition effects are enabled for the swap, adding the modifier transition:true.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder IncludeTransition() => Transition(true);
+
+ ///
+ /// Explicitly ignores transition effects for the swap.
+ ///
+ ///
+ /// This method disables transition effects by adding the modifier transition:false to the swap commands.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder IgnoreTransition() => Transition(false);
+
+ ///
+ /// Allows you to specify that htmx should scroll to the focused element when a request completes.
+ /// htmx preserves focus between requests for inputs that have a defined id attribute. By
+ /// default htmx prevents auto-scrolling to focused inputs between requests which can be
+ /// unwanted behavior on longer requests when the user has already scrolled away.
+ ///
+ ///
+ /// when true will be focus-scroll:true, otherwise when false
+ /// will be focus-scroll:false
+ ///
+ /// Whether to scroll to the focus element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ScrollFocus(bool scroll = true)
+ {
+ AddModifier("focus-scroll", scroll);
+
+ return this;
+ }
+
+ ///
+ /// Explicitly preserves focus between requests for inputs that have a defined id attribute without
+ /// scrolling.
+ ///
+ ///
+ /// Adds a modifier of focus-scroll:false
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder PreserveFocus() => ScrollFocus(false);
+
+ ///
+ /// Specifies a CSS selector to target for the swap operation, smoothly animating the scrollbar position to either the
+ /// top or the bottom of the target element after the swap.
+ ///
+ ///
+ /// Adds a show modifier with the specified CSS selector and scroll direction. For example, if
+ /// is ".item" and is , the modifier show:.item:top
+ /// is added.
+ ///
+ /// The scroll direction after swap.
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowOn(ScrollDirection direction, string? selector = null)
+ {
+ switch (direction)
+ {
+ case ScrollDirection.Top:
+ AddModifier("show", selector is null ? "top" : $"{selector}:top");
+ break;
+ case ScrollDirection.Bottom:
+ AddModifier("show", selector is null ? "bottom" : $"{selector}:bottom");
+ break;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Specifies that the swap should show the top of the element matching the CSS selector.
+ ///
+ ///
+ /// This method adds the modifier show::top, smoothly scrolling to the top of the element identified by
+ /// .
+ ///
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowOnTop(string? selector = null) => ShowOn(ScrollDirection.Top, selector);
+
+ ///
+ /// Specifies that the swap should show the bottom of the element matching the CSS selector.
+ ///
+ ///
+ /// This method adds the modifier show::bottom, smoothly scrolling to the bottom of the element identified by
+ /// .
+ ///
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowOnBottom(string? selector = null) => ShowOn(ScrollDirection.Bottom, selector);
+
+ ///
+ /// Specifies that the swap should show in the window by smoothly scrolling to either the top or bottom of the window.
+ ///
+ /// The direction to scroll the window after the swap.
+ ///
+ /// This method adds the modifier show:window:, directing the swap to display the specified
+ /// element at the bottom of the window.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowWindow(ScrollDirection direction)
+ {
+ switch (direction)
+ {
+ case ScrollDirection.Top:
+ AddModifier("show", "window:top");
+ break;
+ case ScrollDirection.Bottom:
+ AddModifier("show", "window:bottom");
+ break;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Specifies that the swap should smoothly scroll to the top of the window.
+ ///
+ ///
+ /// This method adds the modifier show:window:top, instructing the content to be displayed
+ /// at the top of the window following a swap by smoothly animating the scrollbar position. This can be useful
+ /// for ensuring that important content or notifications at the top of the page are immediately visible to
+ /// the user after a swap operation.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowWindowTop() => ShowWindow(ScrollDirection.Top);
+
+ ///
+ /// Specifies that the swap should smoothly scroll to the bottom of the window.
+ ///
+ ///
+ /// This method adds the modifier show:window:bottom, instructing the content to be displayed
+ /// at the bottom of the window following a swap by smoothly animating the scrollbar position. This positioning
+ /// can be used for infinite scrolling, footers, or information appended at the end of the page.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowWindowBottom() => ShowWindow(ScrollDirection.Bottom);
+
+ ///
+ /// Turns off scrolling after swap.
+ ///
+ ///
+ /// This method disables automatic scrolling by setting the modifier show:none, ensuring the page
+ /// position remains unchanged after the swap.
+ ///
+ /// The SwapStyleBuilder instance for chaining.
+ public SwapStyleBuilder ShowNone()
+ {
+ AddModifier("show", "none");
+
+ return this;
+ }
+
+ ///
+ /// Builds the swap style command string with all specified modifiers.
+ ///
+ /// A tuple containing the SwapStyle and the constructed command string.
+ internal (SwapStyle?, string) Build()
+ {
+ var value = string.Empty;
+
+ if (modifiers.Count > 0)
+ {
+ value = modifiers.Cast()
+ .Select(entry => $"{entry.Key}:{entry.Value}")
+ .Aggregate((current, next) => $"{current} {next}");
+ }
+
+ return (style, value);
+ }
+
+ ///
+ /// Adds a modifier to modifiers, overriding existing values if present
+ ///
+ ///
+ ///
+ private void AddModifier(string modifier, string options)
+ {
+ if (modifiers.Contains(modifier))
+ modifiers.Remove(modifier);
+
+ modifiers.Add(modifier, options);
+ }
+
+ ///
+ /// Adds a boolean modifier to modifiers
+ ///
+ ///
+ ///
+ private void AddModifier(string modifier, bool option) => AddModifier(modifier, option ? "true" : "false");
+}
+
+///
+/// Extension methods for the SwapStyle enum to facilitate building swap style commands.
+///
+public static class SwapStyleBuilderExtension
+{
+ ///
+ /// Modifies the amount of time that htmx will wait after receiving a
+ /// response to swap the content by including the modifier swap:.
+ ///
+ ///
+ /// will be converted to milliseconds if less than 1000, otherwise seconds,
+ /// meaning the resulting modifier will be swap:500ms for a of 500 milliseconds
+ /// or swap:2s for a of 2 seconds..
+ ///
+ /// The swap style.
+ /// The amount of time htmx should wait after receiving a
+ /// response to swap the content.
+ public static SwapStyleBuilder AfterSwapDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSwapDelay(time);
+
+ ///
+ /// Modifies the amount of time that htmx will wait between the swap
+ /// and the settle logic by including the modifier settle:.
+ ///
+ ///
+ /// will be converted to milliseconds if less than 1000, otherwise seconds,
+ /// meaning the resulting modifier will be settle:500ms for a of 500 milliseconds
+ /// or settle:2s for a of 2 seconds..
+ ///
+ /// The swap style.
+ /// The amount of time htmx should wait after receiving a
+ /// response to swap the content.
+ public static SwapStyleBuilder AfterSettleDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSettleDelay(time);
+
+ ///
+ /// Specifies how to set the content scrollbar position after the swap and appends the modifier scroll:.
+ ///
+ ///
+ /// Sets the swapped content scrollbar position after swapping immediately (without animation). For instance, using
+ /// will add the modifier scroll:top which sets the scrollbar position to the top of swap content after the swap.
+ /// If css is present then the page is scrolled to the of the content identified by the css selector.
+ ///
+ /// The swap style.
+ /// The scroll direction after the swap.
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction, string? selector) => new SwapStyleBuilder(style).Scroll(direction, selector);
+
+ ///
+ /// Sets the content scrollbar position to the top of the swapped content after a swap.
+ ///
+ ///
+ /// This method adds the modifier scroll:top to the swap commands, instructing the page to scroll to
+ /// the top of the content after content is swapped immediately and without animation. If css
+ /// is present then the page is scrolled to the top of the content identified by the css selector.
+ ///
+ /// Optional CSS selector of the target element.
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ScrollTop(this SwapStyle style, string? selector) => new SwapStyleBuilder(style).ScrollTop(selector);
+
+ ///
+ /// Sets the content scrollbar position to the bottom of the swapped content after a swap.
+ ///
+ ///
+ /// This method adds the modifier scroll:bottom to the swap commands, instructing the page to scroll to
+ /// the bottom of the content after content is swapped immediately and without animation. If css
+ /// is present then the page is scrolled to the bottom of the content identified by the css selector.
+ ///
+ /// Optional CSS selector of the target element.
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ScrollBottom(this SwapStyle style, string? selector) => new SwapStyleBuilder(style).ScrollBottom(selector);
+
+ ///
+ /// Determines whether to ignore the document title in the swap response by appending the modifier
+ /// ignoreTitle:.
+ ///
+ ///
+ /// When set to true, the document title in the swap response will be ignored by adding the modifier
+ /// ignoreTitle:true.
+ /// This keeps the current title unchanged regardless of the incoming swap content's title tag.
+ ///
+ /// The swap style.
+ /// Whether to ignore the title.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder IgnoreTitle(this SwapStyle style, bool ignore = true) => new SwapStyleBuilder(style).IgnoreTitle(ignore);
+
+ ///
+ /// Includes the document title from the swap response in the current page.
+ ///
+ ///
+ /// This method ensures the title of the document is updated according to the swap response by removing any
+ /// ignoreTitle modifiers, effectively setting ignoreTitle:false.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder IncludeTitle(this SwapStyle style) => new SwapStyleBuilder(style).IncludeTitle();
+
+ ///
+ /// Enables or disables transition effects for the swap by appending the modifier transition:{show}.
+ ///
+ ///
+ /// Controls the display of transition effects during the swap. For example, setting to true
+ /// will add the modifier transition:true to enable smooth transitions.
+ ///
+ /// The swap style.
+ /// Whether to show transition effects.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder Transition(this SwapStyle style, bool show) => new SwapStyleBuilder(style).Transition(show);
+
+ ///
+ /// Explicitly includes transition effects for the swap.
+ ///
+ ///
+ /// By calling this method, transition effects are enabled for the swap, adding the modifier transition:true.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder IncludeTransition(this SwapStyle style) => new SwapStyleBuilder(style).IncludeTransition();
+
+ ///
+ /// Explicitly ignores transition effects for the swap.
+ ///
+ ///
+ /// This method disables transition effects by adding the modifier transition:false to the swap commands.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder IgnoreTransition(this SwapStyle style) => new SwapStyleBuilder(style).IgnoreTransition();
+
+ ///
+ /// Allows you to specify that htmx should scroll to the focused element when a request completes.
+ /// htmx preserves focus between requests for inputs that have a defined id attribute. By
+ /// default htmx prevents auto-scrolling to focused inputs between requests which can be
+ /// unwanted behavior on longer requests when the user has already scrolled away.
+ ///
+ ///
+ /// when true will be focus-scroll:true, otherwise when false
+ /// will be focus-scroll:false
+ ///
+ /// The swap style.
+ /// Whether to scroll to the focus element.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ScrollFocus(this SwapStyle style, bool scroll = true) => new SwapStyleBuilder(style).ScrollFocus(scroll);
+
+ ///
+ /// Explicitly preserves focus between requests for inputs that have a defined id attribute without
+ /// scrolling.
+ ///
+ ///
+ /// Adds a modifier of focus-scroll:false
+ ///
+ /// The swap style.
+ /// Whether to scroll to current focus or preserve focus
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder PreserveFocus(this SwapStyle style, bool scroll = true) => new SwapStyleBuilder(style).PreserveFocus();
+
+ ///
+ /// Specifies a CSS selector to dynamically target for the swap operation, with a scroll direction after the swap.
+ ///
+ ///
+ /// Adds a show modifier with the specified CSS selector and scroll direction. For example, if
+ /// is ".item" and is , the modifier show:.item:top
+ /// is added.
+ ///
+ /// The swap style.
+ /// Optional CSS selector of the target element.
+ /// The scroll direction after swap.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowOn(this SwapStyle style, ScrollDirection direction, string? selector = null) => new SwapStyleBuilder(style).ShowOn(direction, selector);
+
+ ///
+ /// Specifies that the swap should show the element matching the CSS selector at the top of the window.
+ ///
+ ///
+ /// This method adds the modifier show:{selector}:top, directing the swap to display the specified element at the top of the window.
+ ///
+ /// The swap style.
+ /// Optional CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowOnTop(this SwapStyle style, string? selector = null) => new SwapStyleBuilder(style).ShowOnTop(selector);
+
+ ///
+ /// Specifies that the swap should show the element matching the CSS selector at the bottom of the window.
+ ///
+ ///
+ /// This method adds the modifier show:{selector}:bottom, directing the swap to display the specified element at the bottom of the window.
+ ///
+ /// The swap style.
+ /// The CSS selector of the target element.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowOnBottom(this SwapStyle style, string? selector = null) => new SwapStyleBuilder(style).ShowOnBottom(selector);
+
+ ///
+ /// Specifies that the swap should show in the window by smoothly scrolling to either the top or bottom of the window.
+ ///
+ /// The direction to scroll the window after the swap.
+ ///
+ /// This method adds the modifier show:window:, directing the swap to display the specified
+ /// element at the bottom of the window.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowWindow(direction);
+
+ ///
+ /// Specifies that the swap should smoothly scroll to the top of the window.
+ ///
+ ///
+ /// This method adds the modifier show:window:top, instructing the content to be displayed
+ /// at the top of the window following a swap by smoothly animating the scrollbar position. This can be useful
+ /// for ensuring that important content or notifications at the top of the page are immediately visible to
+ /// the user after a swap operation.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowWindowTop(this SwapStyle style) => new SwapStyleBuilder(style).ShowWindowTop();
+
+ ///
+ /// Specifies that the swap should smoothly scroll to the bottom of the window.
+ ///
+ ///
+ /// This method adds the modifier show:window:bottom, instructing the content to be displayed
+ /// at the bottom of the window following a swap by smoothly animating the scrollbar position. This positioning
+ /// can be used for infinite scrolling, footers, or information appended at the end of the page.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowWindowBottom(this SwapStyle style) => new SwapStyleBuilder(style).ShowWindowBottom();
+
+ ///
+ /// Turns off scrolling after swap.
+ ///
+ ///
+ /// This method disables automatic scrolling by setting the modifier show:none, ensuring the page
+ /// position remains unchanged after the swap.
+ ///
+ /// The swap style.
+ /// The SwapStyleBuilder instance for chaining.
+ public static SwapStyleBuilder ShowNone(this SwapStyle style) => new SwapStyleBuilder(style).ShowNone();
+}
\ No newline at end of file
diff --git a/src/Htmxor/ScrollDirection.cs b/src/Htmxor/ScrollDirection.cs
new file mode 100644
index 0000000..3ff2258
--- /dev/null
+++ b/src/Htmxor/ScrollDirection.cs
@@ -0,0 +1,10 @@
+namespace Htmxor;
+
+///
+/// Direction values for scroll and show modifier methods
+///
+public enum ScrollDirection
+{
+ Top,
+ Bottom
+}
diff --git a/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs
new file mode 100644
index 0000000..6f38856
--- /dev/null
+++ b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Bunit;
+
+namespace Htmxor.Http;
+
+public class SwapStyleBuilderTests
+{
+ [Fact]
+ public void SwapStyleBuilder_InitializeAndBuild_ReturnsCorrectValues()
+ {
+ // Arrange
+ var swapStyle = SwapStyle.InnerHTML;
+
+ // Act
+ var builder = new SwapStyleBuilder(swapStyle);
+ var (resultStyle, modifiers) = builder.Build();
+
+ // Assert
+ resultStyle.Should().Be(swapStyle);
+ modifiers.Should().BeEmpty(); // Expect no modifiers if none are added
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_AfterSwap_AddsCorrectDelay()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.AfterSwapDelay(TimeSpan.FromSeconds(1)).Build();
+
+ // Assert
+ modifiers.Should().Be("swap:1s");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_AfterSettle_AddsCorrectDelay()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.AfterSettleDelay(TimeSpan.FromSeconds(1)).Build();
+
+ // Assert
+ modifiers.Should().Be("settle:1s");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_Scroll_AddsCorrectDirection()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.Scroll(ScrollDirection.Bottom).Build();
+
+ // Assert
+ modifiers.Should().Be("scroll:bottom");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_IgnoreTitle_AddsCorrectFlag()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.IgnoreTitle(true).Build();
+
+ // Assert
+ modifiers.Should().Be("ignoreTitle:true");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_Transition_AddsCorrectFlag()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.Transition(true).Build();
+
+ // Assert
+ modifiers.Should().Be("transition:true");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ScrollFocus_AddsCorrectFlag()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ScrollFocus(true).Build();
+
+ // Assert
+ modifiers.Should().Be("focus-scroll:true");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowOn_AddsCorrectSelectorAndDirection()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+ var selector = "#dynamic-area";
+
+ // Act
+ var (_, modifiers) = builder.ShowOn(ScrollDirection.Top, selector).Build();
+
+ // Assert
+ modifiers.Should().Be("show:#dynamic-area:top");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowWindow_AddsCorrectWindowAndDirection()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ShowWindow(ScrollDirection.Top).Build();
+
+ // Assert
+ modifiers.Should().Be("show:window:top");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ChainedOperations_AddsCorrectModifiers()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder
+ .AfterSwapDelay(TimeSpan.FromSeconds(1))
+ .Scroll(ScrollDirection.Top)
+ .Transition(true)
+ .IgnoreTitle(false)
+ .Build();
+
+ // Assert
+ modifiers.Should().Be("swap:1s scroll:top transition:true ignoreTitle:false");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_After_With250Milliseconds_AddsCorrectDelay()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.AfterSwapDelay(TimeSpan.FromMilliseconds(250)).Build();
+
+ // Assert
+ modifiers.Should().Be("swap:250ms");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowOn_BottomDirection_AddsCorrectModifier()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+ var selector = "#element-id";
+
+ // Act
+ var (_, modifiers) = builder.ShowOn(ScrollDirection.Bottom, selector).Build();
+
+ // Assert
+ modifiers.Should().Be("show:#element-id:bottom");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowWindow_BottomDirection_AddsCorrectModifier()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ShowWindow(ScrollDirection.Bottom).Build();
+
+ // Assert
+ modifiers.Should().Be("show:window:bottom");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_NullSwapStyle_ReturnsNullStyle()
+ {
+ // Arrange & Act
+ var builder = new SwapStyleBuilder(null);
+ var (style, _) = builder.Build();
+
+ // Assert
+ style.Should().BeNull();
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowNone_ReturnsCorrectValue()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ShowNone().Build();
+
+ // Assert
+ modifiers.Should().Be("show:none");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowOnTop_ReturnsCorrectValue()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ShowOnTop().Build();
+
+ // Assert
+ modifiers.Should().Be("show:top");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_ShowOnBottom_ReturnsCorrectValue()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ShowOnBottom().Build();
+
+ // Assert
+ modifiers.Should().Be("show:bottom");
+ }
+
+ [Fact]
+ public void SwapStyleBuilder_MixedShowOverrides_ReturnsCorrectValue()
+ {
+ // Arrange
+ var builder = new SwapStyleBuilder(SwapStyle.InnerHTML);
+
+ // Act
+ var (_, modifiers) = builder.ShowOnTop().AfterSettleDelay(TimeSpan.FromMilliseconds(250)).ShowWindowTop().ShowOnBottom().Build();
+
+ // Assert
+ modifiers.Should().Be("settle:250ms show:bottom");
+ }
+}