From eb346801103885efed03b50adc9347d8220ed813 Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Tue, 30 Apr 2024 13:39:07 -0400 Subject: [PATCH 1/7] feat: adds missing reswap modifier and adds reswap builder along with StopPolling This commit introduces several enhancements to the `HtmxResponse` class. It adds a new optional parameter `modifier` to the `Reswap` method, allowing for more flexible response swapping. A new overload of the `Reswap` method is also added that takes a `SwapStyleBuilder` object as an argument. A new method, `StopPolling`, has been introduced which sets the response code to stop polling. Two new classes, `HtmxStatusCodes` and `SwapStyleBuilder`, have been created. The former provides status codes specific to HTMX responses while the latter aids in constructing swap style command strings for HTMX responses. Additionally, a new enum called `ScrollDirection` has been added for specifying scroll direction values. refactor: Add Reswap method and refactor SwapStyleBuilder Added a new Reswap method to the HtmxResponse class, allowing for more flexible response swapping. Refactored the SwapStyleBuilder class to use an OrderedDictionary for storing modifiers instead of a List, improving performance and readability. Also added nullability support for the SwapStyle property in SwapStyleBuilder. Added comprehensive unit tests for these changes in SwapStyleBuilderTests.cs. feat: Add ShowNone method to SwapStyleBuilder A new method, ShowNone, has been added to the SwapStyleBuilder class. This method turns off scrolling after a swap and allows for chaining with other methods in the class. Corresponding unit test has also been added to ensure that it returns the correct value. --- src/Htmxor/Http/HtmxResponse.cs | 43 +++- src/Htmxor/Http/HtmxStatusCodes.cs | 6 + src/Htmxor/Http/SwapStyleBuilder.cs | 192 +++++++++++++++++ src/Htmxor/ScrollDirection.cs | 10 + .../Http/SwapStyleBuilderTests.cs | 200 ++++++++++++++++++ 5 files changed, 449 insertions(+), 2 deletions(-) create mode 100644 src/Htmxor/Http/HtmxStatusCodes.cs create mode 100644 src/Htmxor/Http/SwapStyleBuilder.cs create mode 100644 src/Htmxor/ScrollDirection.cs create mode 100644 test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs 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..49f469d --- /dev/null +++ b/src/Htmxor/Http/SwapStyleBuilder.cs @@ -0,0 +1,192 @@ +using System.Collections; +using System.Collections.Specialized; + +namespace Htmxor.Http; + +/// +/// A builder class for constructing a swap style command string for HTMX responses. +/// +public 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; + } + + /// + /// Adds a delay to the swap operation. + /// + /// The time span to delay the swap. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder After(TimeSpan span) + { + AddModifier("swap", span.TotalMilliseconds < 1000 ? $"{span.TotalMilliseconds}ms" : $"{span.TotalSeconds}s"); + + return this; + } + + /// + /// Specifies the direction to scroll the page after the swap. + /// + /// The scroll direction. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder Scroll(ScrollDirection direction) + { + switch (direction) + { + case ScrollDirection.Top: + AddModifier("scroll", "top"); + break; + case ScrollDirection.Bottom: + AddModifier("scroll", "bottom"); + break; + } + + return this; + } + + /// + /// Determines whether to ignore the document title in the swap response. + /// + /// Whether to ignore the title. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder IgnoreTitle(bool ignore) + { + AddModifier("ignoreTitle", ignore); + + return this; + } + + /// + /// Enables or disables transition effects for the swap. + /// + /// Whether to show transition effects. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder Transition(bool show) + { + AddModifier("transition", show); + + return this; + } + + /// + /// Sets whether to focus and scroll to the swapped content. + /// + /// Whether to scroll to the focus element. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder FocusScroll(bool scroll) + { + AddModifier("focus-scroll", scroll); + + return this; + } + + /// + /// Specifies a CSS selector to dynamically target for the swap operation. + /// + /// The CSS selector of the target element. + /// The scroll direction after swap. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction) + { + switch (direction) + { + case ScrollDirection.Top: + AddModifier("show", $"{selector}:top"); + break; + case ScrollDirection.Bottom: + AddModifier("show", $"{selector}:bottom"); + break; + } + + return this; + } + + /// + /// Specifies that the swap should show in the window, with an optional scroll direction. + /// + /// The direction to scroll the window after the swap. + /// 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; + } + + /// + /// Turns off scrolling after 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); + } + + private void AddModifier(string modifier, bool option) => AddModifier(modifier, option.ToString().ToLowerInvariant()); +} + +/// +/// Extension methods for the SwapStyle enum to facilitate building swap style commands. +/// +public static class SwapStyleBuilderExtension +{ + // Each method below returns a SwapStyleBuilder instance initialized with the respective SwapStyle + // and applies the specified modifier to it. This allows for fluent configuration of swap style commands. + + public static SwapStyleBuilder After(this SwapStyle style, TimeSpan span) => new SwapStyleBuilder(style).After(span); + public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).Scroll(direction); + public static SwapStyleBuilder IgnoreTitle(this SwapStyle style, bool ignore) => new SwapStyleBuilder(style).IgnoreTitle(ignore); + public static SwapStyleBuilder Transition(this SwapStyle style, bool show) => new SwapStyleBuilder(style).Transition(show); + public static SwapStyleBuilder FocusScroll(this SwapStyle style, bool scroll) => new SwapStyleBuilder(style).FocusScroll(scroll); + public static SwapStyleBuilder ShowOn(this SwapStyle style, string selector, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(selector, direction); + public static SwapStyleBuilder ShowWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowWindow(direction); +} \ 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..e9bcda7 --- /dev/null +++ b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Bunit; + +namespace Htmxor.Http; + +public class SwapStyleBuilderTests : TestContext +{ + [Fact] + public void SwapStyleBuilder_InitializeAndBuild_ReturnsCorrectValues() + { + // Arrange + var swapStyle = SwapStyle.InnerHTML; + + // Act + var builder = new SwapStyleBuilder(swapStyle); + var (resultStyle, modifiers) = builder.Build(); + + // Assert + Assert.Equal(swapStyle, resultStyle); + Assert.Empty(modifiers); // Expect no modifiers if none are added + } + + [Fact] + public void SwapStyleBuilder_After_AddsCorrectDelay() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.After(TimeSpan.FromSeconds(1)).Build(); + + // Assert + Assert.Equal("swap:1s", modifiers); + } + + [Fact] + public void SwapStyleBuilder_Scroll_AddsCorrectDirection() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.Scroll(ScrollDirection.Bottom).Build(); + + // Assert + Assert.Equal("scroll:bottom", modifiers); + } + + [Fact] + public void SwapStyleBuilder_IgnoreTitle_AddsCorrectFlag() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.IgnoreTitle(true).Build(); + + // Assert + Assert.Equal("ignoreTitle:true", modifiers); + } + + [Fact] + public void SwapStyleBuilder_Transition_AddsCorrectFlag() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.Transition(true).Build(); + + // Assert + Assert.Equal("transition:true", modifiers); + } + + [Fact] + public void SwapStyleBuilder_FocusScroll_AddsCorrectFlag() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.FocusScroll(true).Build(); + + // Assert + Assert.Equal("focus-scroll:true", modifiers); + } + + [Fact] + public void SwapStyleBuilder_ShowOn_AddsCorrectSelectorAndDirection() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + var selector = "#dynamic-area"; + + // Act + var (_, modifiers) = builder.ShowOn(selector, ScrollDirection.Top).Build(); + + // Assert + Assert.Equal("show:#dynamic-area:top", modifiers); + } + + [Fact] + public void SwapStyleBuilder_ShowWindow_AddsCorrectWindowAndDirection() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.ShowWindow(ScrollDirection.Top).Build(); + + // Assert + Assert.Equal("show:window:top", modifiers); + } + + [Fact] + public void SwapStyleBuilder_ChainedOperations_AddsCorrectModifiers() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder + .After(TimeSpan.FromSeconds(1)) + .Scroll(ScrollDirection.Top) + .Transition(true) + .IgnoreTitle(false) + .Build(); + + // Assert + Assert.Equal("swap:1s scroll:top transition:true ignoreTitle:false", modifiers); + } + + [Fact] + public void SwapStyleBuilder_After_With250Milliseconds_AddsCorrectDelay() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.After(TimeSpan.FromMilliseconds(250)).Build(); + + // Assert + Assert.Equal("swap:250ms", modifiers); + } + + [Fact] + public void SwapStyleBuilder_ShowOn_BottomDirection_AddsCorrectModifier() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + var selector = "#element-id"; + + // Act + var (_, modifiers) = builder.ShowOn(selector, ScrollDirection.Bottom).Build(); + + // Assert + Assert.Equal("show:#element-id:bottom", modifiers); + } + + [Fact] + public void SwapStyleBuilder_ShowWindow_BottomDirection_AddsCorrectModifier() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.ShowWindow(ScrollDirection.Bottom).Build(); + + // Assert + Assert.Equal("show:window:bottom", modifiers); + } + + [Fact] + public void SwapStyleBuilder_NullSwapStyle_ReturnsNullStyle() + { + // Arrange & Act + var builder = new SwapStyleBuilder(null); + var (style, _) = builder.Build(); + + // Assert + Assert.Null(style); + } + + [Fact] + public void SwapStyleBuilder_ShowNone_ReturnsCorrectValue() + { + // Arrange + var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); + + // Act + var (_, modifiers) = builder.ShowNone().Build(); + + // Assert + Assert.Equal("show:none", modifiers); + } +} From aaa58070759eb71d3d8d1f4b12453d06d1b652d9 Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Tue, 30 Apr 2024 13:59:49 -0400 Subject: [PATCH 2/7] feat: Add ShowNone method to SwapStyleBuilder extension --- src/Htmxor/Http/SwapStyleBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Htmxor/Http/SwapStyleBuilder.cs b/src/Htmxor/Http/SwapStyleBuilder.cs index 49f469d..4bad359 100644 --- a/src/Htmxor/Http/SwapStyleBuilder.cs +++ b/src/Htmxor/Http/SwapStyleBuilder.cs @@ -189,4 +189,5 @@ public static class SwapStyleBuilderExtension public static SwapStyleBuilder FocusScroll(this SwapStyle style, bool scroll) => new SwapStyleBuilder(style).FocusScroll(scroll); public static SwapStyleBuilder ShowOn(this SwapStyle style, string selector, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(selector, direction); public static SwapStyleBuilder ShowWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowWindow(direction); + public static SwapStyleBuilder ShowNone(this SwapStyle style) => new SwapStyleBuilder(style).ShowNone(); } \ No newline at end of file From 76509fb1de4c1ecd90a6bbc79064916223be35c1 Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Tue, 30 Apr 2024 19:33:49 -0400 Subject: [PATCH 3/7] refactor: wip enhance SwapStyleBuilder functionality Significant enhancements have been made to the SwapStyleBuilder class. The class has been sealed and several new methods have been added to provide more granular control over swap style commands. These include methods for controlling scroll direction, transition effects, title inclusion, and delay times for both swap and settle operations. Existing methods have also been updated with more detailed comments and improved parameter naming for better clarity. --- src/Htmxor/Http/SwapStyleBuilder.cs | 202 +++++++++++++++++++++++++--- 1 file changed, 180 insertions(+), 22 deletions(-) diff --git a/src/Htmxor/Http/SwapStyleBuilder.cs b/src/Htmxor/Http/SwapStyleBuilder.cs index 4bad359..f8ffb26 100644 --- a/src/Htmxor/Http/SwapStyleBuilder.cs +++ b/src/Htmxor/Http/SwapStyleBuilder.cs @@ -6,7 +6,7 @@ namespace Htmxor.Http; /// /// A builder class for constructing a swap style command string for HTMX responses. /// -public class SwapStyleBuilder +public sealed class SwapStyleBuilder { private readonly SwapStyle? style; private readonly OrderedDictionary modifiers = new(); @@ -21,21 +21,50 @@ public SwapStyleBuilder(SwapStyle? style = null) } /// - /// Adds a delay to the swap operation. + /// Modifies the amount of time that htmx will wait after receiving a + /// response to swap the content by including the modifier swap:. /// - /// The time span to delay the swap. - /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder After(TimeSpan span) + /// + /// 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 SwapStyleBuilder AfterSwapDelay(TimeSpan time) { - AddModifier("swap", span.TotalMilliseconds < 1000 ? $"{span.TotalMilliseconds}ms" : $"{span.TotalSeconds}s"); + AddModifier("swap", time.TotalMilliseconds < 1000 ? $"{time.TotalMilliseconds}ms" : $"{time.TotalSeconds}s"); return this; } /// - /// Specifies the direction to scroll the page after the swap. + /// 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 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 AfterSettleDelay(TimeSpan time) + { + AddModifier("settle", time.TotalMilliseconds < 1000 ? $"{time.TotalMilliseconds}ms" : $"{time.TotalSeconds}s"); + + return this; + } + + /// + /// Specifies the direction to scroll the page after the swap and appends the modifier scroll:{direction}. /// - /// The scroll direction. + /// + /// Sets the scroll direction on the page after swapping. For instance, using + /// will add the modifier scroll:top which instructs the page to scroll to the top after the swap. + /// + /// The scroll direction after the swap. /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder Scroll(ScrollDirection direction) { @@ -53,11 +82,37 @@ public SwapStyleBuilder Scroll(ScrollDirection direction) } /// - /// Determines whether to ignore the document title in the swap response. + /// Automatically scrolls to the top of the page after a swap. + /// + /// + /// This method adds the modifier scroll:top to the swap commands, instructing the page to scroll to + /// the top after content is swapped. + /// + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ScrollTop() => Scroll(ScrollDirection.Top); + + /// + /// Automatically scrolls to the bottom of the page after a swap. + /// + /// + /// This method adds the modifier scroll:bottom to the swap commands, instructing the page to scroll to + /// the bottom after content is swapped. + /// + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ScrollBottom() => Scroll(ScrollDirection.Bottom); + + /// + /// Determines whether to ignore the document title in the swap response by appending the modifier + /// ignoreTitle:{ignore}. /// + /// + /// 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) + public SwapStyleBuilder IgnoreTitle(bool ignore = true) { AddModifier("ignoreTitle", ignore); @@ -65,8 +120,22 @@ public SwapStyleBuilder IgnoreTitle(bool ignore) } /// - /// Enables or disables transition effects for the swap. + /// 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:{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. + /// /// Whether to show transition effects. /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder Transition(bool show) @@ -77,20 +146,49 @@ public SwapStyleBuilder Transition(bool show) } /// - /// Sets whether to focus and scroll to the swapped content. + /// 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); + + /// + /// 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 FocusScroll(bool scroll) + public SwapStyleBuilder ScrollFocus(bool scroll = true) { AddModifier("focus-scroll", scroll); return this; } + public SwapStyleBuilder PreserveFocus() => ScrollFocus(false); + /// - /// Specifies a CSS selector to dynamically target for the swap operation. + /// Specifies a CSS selector to dynamically target for the swap operation, with an optional 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 CSS selector of the target element. /// The scroll direction after swap. /// The SwapStyleBuilder instance for chaining. @@ -109,12 +207,35 @@ public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction) return this; } + /// + /// 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 CSS selector of the target element. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOnTop(string selector) => ShowOn(selector, ScrollDirection.Top); + + /// + /// 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 CSS selector of the target element. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOnBottom(string selector) => ShowOn(selector, ScrollDirection.Bottom); + /// /// Specifies that the swap should show in the window, with an optional scroll direction. /// /// The direction to scroll the window after the swap. + /// + /// This method adds the modifier show:window:{direction}, directing the swap to display the specified element at the bottom of the window. + /// /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowWindow(ScrollDirection direction) + public SwapStyleBuilder ShowOnWindow(ScrollDirection direction) { switch (direction) { @@ -130,8 +251,34 @@ public SwapStyleBuilder ShowWindow(ScrollDirection direction) } /// - /// Turns off scrolling after swap + /// Specifies that the swap should show content at 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. This can be useful for ensuring that important content or + /// notifications are immediately visible to the user after a swap operation. + /// + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOnWindowTop() => ShowOnWindow(ScrollDirection.Top); + + /// + /// Specifies that the swap should show content at 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. This positioning can be used for infinite scrolling, footers or + /// lower-priority information that should not immediately distract from other content. + /// + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOnWindowBottom() => ShowOnWindow(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() { @@ -167,11 +314,11 @@ private void AddModifier(string modifier, string options) { if (modifiers.Contains(modifier)) modifiers.Remove(modifier); - + modifiers.Add(modifier, options); } - private void AddModifier(string modifier, bool option) => AddModifier(modifier, option.ToString().ToLowerInvariant()); + private void AddModifier(string modifier, bool option) => AddModifier(modifier, option ? "true" : "false"); } /// @@ -182,12 +329,23 @@ public static class SwapStyleBuilderExtension // Each method below returns a SwapStyleBuilder instance initialized with the respective SwapStyle // and applies the specified modifier to it. This allows for fluent configuration of swap style commands. - public static SwapStyleBuilder After(this SwapStyle style, TimeSpan span) => new SwapStyleBuilder(style).After(span); + public static SwapStyleBuilder AfterSwapDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSwapDelay(time); + public static SwapStyleBuilder AfterSettleDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSettleDelay(time); public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).Scroll(direction); - public static SwapStyleBuilder IgnoreTitle(this SwapStyle style, bool ignore) => new SwapStyleBuilder(style).IgnoreTitle(ignore); + public static SwapStyleBuilder ScrollTop(this SwapStyle style) => new SwapStyleBuilder(style).ScrollTop(); + public static SwapStyleBuilder ScrollBottom(this SwapStyle style) => new SwapStyleBuilder(style).ScrollBottom(); + public static SwapStyleBuilder IgnoreTitle(this SwapStyle style, bool ignore = true) => new SwapStyleBuilder(style).IgnoreTitle(ignore); + public static SwapStyleBuilder IncludeTitle(this SwapStyle style) => new SwapStyleBuilder(style).IncludeTitle(); public static SwapStyleBuilder Transition(this SwapStyle style, bool show) => new SwapStyleBuilder(style).Transition(show); - public static SwapStyleBuilder FocusScroll(this SwapStyle style, bool scroll) => new SwapStyleBuilder(style).FocusScroll(scroll); + public static SwapStyleBuilder IncludeTransition(this SwapStyle style) => new SwapStyleBuilder(style).IncludeTransition(); + public static SwapStyleBuilder IgnoreTransition(this SwapStyle style) => new SwapStyleBuilder(style).IgnoreTransition(); + public static SwapStyleBuilder ScrollFocus(this SwapStyle style, bool scroll = true) => new SwapStyleBuilder(style).ScrollFocus(scroll); + public static SwapStyleBuilder PreserveFocus(this SwapStyle style, bool scroll = true) => new SwapStyleBuilder(style).PreserveFocus(); public static SwapStyleBuilder ShowOn(this SwapStyle style, string selector, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(selector, direction); - public static SwapStyleBuilder ShowWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowWindow(direction); + public static SwapStyleBuilder ShowOnTop(this SwapStyle style, string selector) => new SwapStyleBuilder(style).ShowOnTop(selector); + public static SwapStyleBuilder ShowOnBottom(this SwapStyle style, string selector) => new SwapStyleBuilder(style).ShowOnBottom(selector); + public static SwapStyleBuilder ShowOnWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOnWindow(direction); + public static SwapStyleBuilder ShowOnWindowTop(this SwapStyle style) => new SwapStyleBuilder(style).ShowOnWindowTop(); + public static SwapStyleBuilder ShowOnWindowBottom(this SwapStyle style) => new SwapStyleBuilder(style).ShowOnWindowBottom(); public static SwapStyleBuilder ShowNone(this SwapStyle style) => new SwapStyleBuilder(style).ShowNone(); } \ No newline at end of file From a01abc02ec0dee120be20fb21bedbfc8f529ec97 Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Tue, 30 Apr 2024 22:13:48 -0400 Subject: [PATCH 4/7] feat: enhance SwapStyleBuilder functionality Enhanced the SwapStyleBuilder class by adding new methods to control viewport positioning after a swap operation. This includes methods for specifying whether the viewport should be positioned at the top or bottom of the swap target, and whether it should scroll to a focused element when a request completes. Also added corresponding tests to verify these new features. --- src/Htmxor/Http/SwapStyleBuilder.cs | 310 +++++++++++++++++- .../Http/SwapStyleBuilderTests.cs | 55 ++-- 2 files changed, 330 insertions(+), 35 deletions(-) diff --git a/src/Htmxor/Http/SwapStyleBuilder.cs b/src/Htmxor/Http/SwapStyleBuilder.cs index f8ffb26..4e10ac6 100644 --- a/src/Htmxor/Http/SwapStyleBuilder.cs +++ b/src/Htmxor/Http/SwapStyleBuilder.cs @@ -29,7 +29,6 @@ public SwapStyleBuilder(SwapStyle? style = null) /// 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 SwapStyleBuilder AfterSwapDelay(TimeSpan time) @@ -164,6 +163,7 @@ public SwapStyleBuilder Transition(bool show) 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. @@ -181,10 +181,63 @@ public SwapStyleBuilder ScrollFocus(bool scroll = true) 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 dynamically target for the swap operation, with an optional scroll direction after the swap. + /// Positions viewport at top of swap target after swap is completed + /// + /// + /// Adds a show modifier with the specified scroll direction. + /// If is , + /// the modifier show:top is added. Otherwise, show:bottom + /// is added + /// + /// The scroll direction after swap. + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOn(ScrollDirection direction) + { + switch (direction) + { + case ScrollDirection.Top: + AddModifier("show", $"top"); + break; + case ScrollDirection.Bottom: + AddModifier("show", $"bottom"); + break; + } + + return this; + } + + /// + /// Positions viewport at top of swap target after swap is completed + /// + /// + /// Adds a show modifier show:top + /// + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOnTop() => ShowOn(ScrollDirection.Top); + + /// + /// Positions viewport at top of swap target after swap is completed + /// + /// + /// Adds a show modifier with the specified scroll direction. + /// Adds a show modifier show:bottom + /// + /// The SwapStyleBuilder instance for chaining. + public SwapStyleBuilder ShowOnBottom() => ShowOn(ScrollDirection.Bottom); + + /// + /// 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. @@ -235,15 +288,15 @@ public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction) /// This method adds the modifier show:window:{direction}, directing the swap to display the specified element at the bottom of the window. /// /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnWindow(ScrollDirection direction) + public SwapStyleBuilder ShowWindow(ScrollDirection direction) { switch (direction) { case ScrollDirection.Top: - AddModifier("show", $"window:top"); + AddModifier("show", "window:top"); break; case ScrollDirection.Bottom: - AddModifier("show", $"window:bottom"); + AddModifier("show", "window:bottom"); break; } @@ -256,10 +309,10 @@ public SwapStyleBuilder ShowOnWindow(ScrollDirection direction) /// /// This method adds the modifier show:window:top, instructing the content to be displayed /// at the top of the window following a swap. This can be useful for ensuring that important content or - /// notifications are immediately visible to the user after a swap operation. + /// notifications at the top of the page are immediately visible to the user after a swap operation. /// /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnWindowTop() => ShowOnWindow(ScrollDirection.Top); + public SwapStyleBuilder ShowWindowTop() => ShowWindow(ScrollDirection.Top); /// /// Specifies that the swap should show content at the bottom of the window. @@ -270,7 +323,7 @@ public SwapStyleBuilder ShowOnWindow(ScrollDirection direction) /// lower-priority information that should not immediately distract from other content. /// /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnWindowBottom() => ShowOnWindow(ScrollDirection.Bottom); + public SwapStyleBuilder ShowWindowBottom() => ShowWindow(ScrollDirection.Bottom); /// /// Turns off scrolling after swap. @@ -318,6 +371,11 @@ private void AddModifier(string modifier, string options) modifiers.Add(modifier, options); } + /// + /// Adds a boolean modifier to modifiers + /// + /// + /// private void AddModifier(string modifier, bool option) => AddModifier(modifier, option ? "true" : "false"); } @@ -326,26 +384,250 @@ private void AddModifier(string modifier, string options) /// public static class SwapStyleBuilderExtension { - // Each method below returns a SwapStyleBuilder instance initialized with the respective SwapStyle - // and applies the specified modifier to it. This allows for fluent configuration of swap style commands. - + /// + /// 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 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 AfterSettleDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSettleDelay(time); + + /// + /// Specifies the direction to scroll the page after the swap and appends the modifier scroll:{direction}. + /// + /// + /// Sets the scroll direction on the page after swapping. For instance, using + /// will add the modifier scroll:top which instructs the page to scroll to the top after the swap. + /// + /// The swap style. + /// The scroll direction after the swap. + /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).Scroll(direction); + + /// + /// Automatically scrolls to the top of the page after a swap. + /// + /// + /// This method adds the modifier scroll:top to the swap commands, instructing the page to scroll to + /// the top after content is swapped. + /// + /// The swap style. + /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder ScrollTop(this SwapStyle style) => new SwapStyleBuilder(style).ScrollTop(); + + /// + /// Automatically scrolls to the bottom of the page after a swap. + /// + /// + /// This method adds the modifier scroll:bottom to the swap commands, instructing the page to scroll to + /// the bottom after content is swapped. + /// + /// The swap style. + /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder ScrollBottom(this SwapStyle style) => new SwapStyleBuilder(style).ScrollBottom(); + + /// + /// Determines whether to ignore the document title in the swap response by appending the modifier + /// ignoreTitle:{ignore}. + /// + /// + /// 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(); + + /// + /// 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. Allows you + /// to specify that htmx should scroll to the focused element when a request completes + /// + /// + /// 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(); + + /// + /// Positions viewport at top of swap target after swap is completed + /// + /// + /// Adds a show modifier show:top + /// + /// The swap style. + /// The scroll direction after swap. + /// The SwapStyleBuilder instance for chaining. + public static SwapStyleBuilder ShowOnTop(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(ScrollDirection.Top); + + /// + /// Positions viewport at top of swap target after swap is completed + /// + /// + /// Adds a show modifier show:bottom + /// + /// The swap style. + /// The SwapStyleBuilder instance for chaining. + public static SwapStyleBuilder ShowOnBottom(this SwapStyle style) => new SwapStyleBuilder(style).ShowOn(ScrollDirection.Bottom); + + /// + /// 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. + /// The CSS selector of the target element. + /// The scroll direction after swap. + /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder ShowOn(this SwapStyle style, string selector, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(selector, direction); + + /// + /// 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. + /// The CSS selector of the target element. + /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder ShowOnTop(this SwapStyle style, string selector) => 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) => new SwapStyleBuilder(style).ShowOnBottom(selector); - public static SwapStyleBuilder ShowOnWindow(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOnWindow(direction); - public static SwapStyleBuilder ShowOnWindowTop(this SwapStyle style) => new SwapStyleBuilder(style).ShowOnWindowTop(); - public static SwapStyleBuilder ShowOnWindowBottom(this SwapStyle style) => new SwapStyleBuilder(style).ShowOnWindowBottom(); + + /// + /// Specifies that the swap should show in the window, with an optional scroll direction. + /// + /// The direction to scroll the window after the swap. + /// + /// This method adds the modifier show:window:{direction}, 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 show content at 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. 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 show content at 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. This positioning can be used for infinite scrolling, footers or + /// lower-priority information that should not immediately distract from other content. + /// + /// 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/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs index e9bcda7..c77a5a7 100644 --- a/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs +++ b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs @@ -20,21 +20,34 @@ public void SwapStyleBuilder_InitializeAndBuild_ReturnsCorrectValues() var (resultStyle, modifiers) = builder.Build(); // Assert - Assert.Equal(swapStyle, resultStyle); - Assert.Empty(modifiers); // Expect no modifiers if none are added + resultStyle.Should().Be(swapStyle); + modifiers.Should().BeEmpty(); // Expect no modifiers if none are added } [Fact] - public void SwapStyleBuilder_After_AddsCorrectDelay() + public void SwapStyleBuilder_AfterSwap_AddsCorrectDelay() { // Arrange var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); // Act - var (_, modifiers) = builder.After(TimeSpan.FromSeconds(1)).Build(); + var (_, modifiers) = builder.AfterSwapDelay(TimeSpan.FromSeconds(1)).Build(); // Assert - Assert.Equal("swap:1s", modifiers); + 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] @@ -47,7 +60,7 @@ public void SwapStyleBuilder_Scroll_AddsCorrectDirection() var (_, modifiers) = builder.Scroll(ScrollDirection.Bottom).Build(); // Assert - Assert.Equal("scroll:bottom", modifiers); + modifiers.Should().Be("scroll:bottom"); } [Fact] @@ -60,7 +73,7 @@ public void SwapStyleBuilder_IgnoreTitle_AddsCorrectFlag() var (_, modifiers) = builder.IgnoreTitle(true).Build(); // Assert - Assert.Equal("ignoreTitle:true", modifiers); + modifiers.Should().Be("ignoreTitle:true"); } [Fact] @@ -73,20 +86,20 @@ public void SwapStyleBuilder_Transition_AddsCorrectFlag() var (_, modifiers) = builder.Transition(true).Build(); // Assert - Assert.Equal("transition:true", modifiers); + modifiers.Should().Be("transition:true"); } [Fact] - public void SwapStyleBuilder_FocusScroll_AddsCorrectFlag() + public void SwapStyleBuilder_ScrollFocus_AddsCorrectFlag() { // Arrange var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); // Act - var (_, modifiers) = builder.FocusScroll(true).Build(); + var (_, modifiers) = builder.ScrollFocus(true).Build(); // Assert - Assert.Equal("focus-scroll:true", modifiers); + modifiers.Should().Be("focus-scroll:true"); } [Fact] @@ -100,7 +113,7 @@ public void SwapStyleBuilder_ShowOn_AddsCorrectSelectorAndDirection() var (_, modifiers) = builder.ShowOn(selector, ScrollDirection.Top).Build(); // Assert - Assert.Equal("show:#dynamic-area:top", modifiers); + modifiers.Should().Be("show:#dynamic-area:top"); } [Fact] @@ -113,7 +126,7 @@ public void SwapStyleBuilder_ShowWindow_AddsCorrectWindowAndDirection() var (_, modifiers) = builder.ShowWindow(ScrollDirection.Top).Build(); // Assert - Assert.Equal("show:window:top", modifiers); + modifiers.Should().Be("show:window:top"); } [Fact] @@ -124,14 +137,14 @@ public void SwapStyleBuilder_ChainedOperations_AddsCorrectModifiers() // Act var (_, modifiers) = builder - .After(TimeSpan.FromSeconds(1)) + .AfterSwapDelay(TimeSpan.FromSeconds(1)) .Scroll(ScrollDirection.Top) .Transition(true) .IgnoreTitle(false) .Build(); // Assert - Assert.Equal("swap:1s scroll:top transition:true ignoreTitle:false", modifiers); + modifiers.Should().Be("swap:1s scroll:top transition:true ignoreTitle:false"); } [Fact] @@ -141,10 +154,10 @@ public void SwapStyleBuilder_After_With250Milliseconds_AddsCorrectDelay() var builder = new SwapStyleBuilder(SwapStyle.InnerHTML); // Act - var (_, modifiers) = builder.After(TimeSpan.FromMilliseconds(250)).Build(); + var (_, modifiers) = builder.AfterSwapDelay(TimeSpan.FromMilliseconds(250)).Build(); // Assert - Assert.Equal("swap:250ms", modifiers); + modifiers.Should().Be("swap:250ms"); } [Fact] @@ -158,7 +171,7 @@ public void SwapStyleBuilder_ShowOn_BottomDirection_AddsCorrectModifier() var (_, modifiers) = builder.ShowOn(selector, ScrollDirection.Bottom).Build(); // Assert - Assert.Equal("show:#element-id:bottom", modifiers); + modifiers.Should().Be("show:#element-id:bottom"); } [Fact] @@ -171,7 +184,7 @@ public void SwapStyleBuilder_ShowWindow_BottomDirection_AddsCorrectModifier() var (_, modifiers) = builder.ShowWindow(ScrollDirection.Bottom).Build(); // Assert - Assert.Equal("show:window:bottom", modifiers); + modifiers.Should().Be("show:window:bottom"); } [Fact] @@ -182,7 +195,7 @@ public void SwapStyleBuilder_NullSwapStyle_ReturnsNullStyle() var (style, _) = builder.Build(); // Assert - Assert.Null(style); + style.Should().BeNull(); } [Fact] @@ -195,6 +208,6 @@ public void SwapStyleBuilder_ShowNone_ReturnsCorrectValue() var (_, modifiers) = builder.ShowNone().Build(); // Assert - Assert.Equal("show:none", modifiers); + modifiers.Should().Be("show:none"); } } From 674a877837ebe9de1e04c44cd773c7aeea4f366a Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Tue, 30 Apr 2024 22:22:34 -0400 Subject: [PATCH 5/7] test: add new SwapStyleBuilder tests New unit tests have been added to the `SwapStyleBuilderTests` class. These tests cover the `ShowOnTop`, `ShowOnBottom`, and `MixedShowOverrides` methods, ensuring they return correct values. The inheritance from `TestContext` has also been removed from this class. --- .../Http/SwapStyleBuilderTests.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs index c77a5a7..5146512 100644 --- a/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs +++ b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs @@ -7,7 +7,7 @@ namespace Htmxor.Http; -public class SwapStyleBuilderTests : TestContext +public class SwapStyleBuilderTests { [Fact] public void SwapStyleBuilder_InitializeAndBuild_ReturnsCorrectValues() @@ -210,4 +210,43 @@ public void SwapStyleBuilder_ShowNone_ReturnsCorrectValue() // 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"); + } } From d039d79a9ae56307e735a8ab8cd5780db25d66df Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Wed, 1 May 2024 11:29:10 -0400 Subject: [PATCH 6/7] refactor: Update SwapStyleBuilder methods for better clarity Updated the SwapStyleBuilder class and its extension to improve code readability and maintainability. Changes include: - Modified method descriptions to provide more accurate information about their functionality. - Updated parameter names and descriptions for better understanding of their purpose. - Enhanced remarks sections with additional details about the methods' behavior. --- src/Htmxor/Http/SwapStyleBuilder.cs | 131 +++++++++++++++++----------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/src/Htmxor/Http/SwapStyleBuilder.cs b/src/Htmxor/Http/SwapStyleBuilder.cs index 4e10ac6..2aeed94 100644 --- a/src/Htmxor/Http/SwapStyleBuilder.cs +++ b/src/Htmxor/Http/SwapStyleBuilder.cs @@ -44,8 +44,8 @@ public SwapStyleBuilder AfterSwapDelay(TimeSpan time) /// /// /// 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.. + /// 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. @@ -57,11 +57,11 @@ public SwapStyleBuilder AfterSettleDelay(TimeSpan time) } /// - /// Specifies the direction to scroll the page after the swap and appends the modifier scroll:{direction}. + /// Specifies how to set the current window scrollbar after the swap and appends the modifier scroll:. /// /// - /// Sets the scroll direction on the page after swapping. For instance, using - /// will add the modifier scroll:top which instructs the page to scroll to the top after the swap. + /// Sets the window scrollbar position after swapping immediately (without animation). For instance, using + /// will add the modifier scroll:top which instructs the window to set the scrollbar position to the top of swap content after the swap. /// /// The scroll direction after the swap. /// The SwapStyleBuilder instance for chaining. @@ -81,28 +81,28 @@ public SwapStyleBuilder Scroll(ScrollDirection direction) } /// - /// Automatically scrolls to the top of the page after a swap. + /// Sets the window 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 after content is swapped. + /// the top after content is swapped immediately and without animation. /// /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder ScrollTop() => Scroll(ScrollDirection.Top); /// - /// Automatically scrolls to the bottom of the page after a swap. + /// Sets the window 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 after content is swapped. + /// the bottom after content is swapped immediately and without animation. /// /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder ScrollBottom() => Scroll(ScrollDirection.Bottom); /// /// Determines whether to ignore the document title in the swap response by appending the modifier - /// ignoreTitle:{ignore}. + /// ignoreTitle:. /// /// /// When set to true, the document title in the swap response will be ignored by adding the modifier @@ -129,7 +129,7 @@ public SwapStyleBuilder IgnoreTitle(bool ignore = true) public SwapStyleBuilder IncludeTitle() => IgnoreTitle(false); /// - /// Enables or disables transition effects for the swap by appending the modifier transition:{show}. + /// 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 @@ -192,7 +192,7 @@ public SwapStyleBuilder ScrollFocus(bool scroll = true) public SwapStyleBuilder PreserveFocus() => ScrollFocus(false); /// - /// Positions viewport at top of swap target after swap is completed + /// Smoothly animates the scrollbar position to top or bottom of swap target after swap is completed /// /// /// Adds a show modifier with the specified scroll direction. @@ -218,7 +218,7 @@ public SwapStyleBuilder ShowOn(ScrollDirection direction) } /// - /// Positions viewport at top of swap target after swap is completed + /// Smoothly animates the scrollbar position to top of swap target after swap is completed /// /// /// Adds a show modifier show:top @@ -227,7 +227,7 @@ public SwapStyleBuilder ShowOn(ScrollDirection direction) public SwapStyleBuilder ShowOnTop() => ShowOn(ScrollDirection.Top); /// - /// Positions viewport at top of swap target after swap is completed + /// Smoothly animates the scrollbar position to bottom of swap target after swap is completed /// /// /// Adds a show modifier with the specified scroll direction. @@ -237,10 +237,13 @@ public SwapStyleBuilder ShowOn(ScrollDirection direction) public SwapStyleBuilder ShowOnBottom() => ShowOn(ScrollDirection.Bottom); /// - /// Specifies a CSS selector to dynamically target for the swap operation, with a scroll direction after the swap. + /// 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. + /// 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 CSS selector of the target element. /// The scroll direction after swap. @@ -261,31 +264,34 @@ public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction) } /// - /// Specifies that the swap should show the element matching the CSS selector at the top of the window. + /// Specifies that the swap should show the top of the element matching the CSS selector. /// /// - /// This method adds the modifier show:{selector}:top, directing the swap to display the specified element at the top of the window. + /// This method adds the modifier show::top, smoothly scrolling to the top of the element identified by + /// . /// /// The CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder ShowOnTop(string selector) => ShowOn(selector, ScrollDirection.Top); /// - /// Specifies that the swap should show the element matching the CSS selector at the bottom of the window. + /// Specifies that the swap should show the bottom of the element matching the CSS selector. /// /// - /// This method adds the modifier show:{selector}:bottom, directing the swap to display the specified element at the bottom of the window. + /// This method adds the modifier show::bottom, smoothly scrolling to the bottom of the element identified by + /// . /// /// The CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder ShowOnBottom(string selector) => ShowOn(selector, ScrollDirection.Bottom); /// - /// Specifies that the swap should show in the window, with an optional scroll direction. + /// 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:{direction}, directing the swap to display the specified element at the bottom of the window. + /// 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) @@ -304,23 +310,24 @@ public SwapStyleBuilder ShowWindow(ScrollDirection direction) } /// - /// Specifies that the swap should show content at the top of the window. + /// 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. 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. + /// 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 show content at the bottom of the window. + /// 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. This positioning can be used for infinite scrolling, footers or - /// lower-priority information that should not immediately distract from other content. + /// 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); @@ -404,8 +411,8 @@ public static class SwapStyleBuilderExtension /// /// /// 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.. + /// 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 @@ -413,11 +420,11 @@ public static class SwapStyleBuilderExtension public static SwapStyleBuilder AfterSettleDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSettleDelay(time); /// - /// Specifies the direction to scroll the page after the swap and appends the modifier scroll:{direction}. + /// Specifies how to set the current window scrollbar after the swap and appends the modifier scroll:. /// /// - /// Sets the scroll direction on the page after swapping. For instance, using - /// will add the modifier scroll:top which instructs the page to scroll to the top after the swap. + /// Sets the window scrollbar position after swapping immediately (without animation). For instance, using + /// will add the modifier scroll:top which instructs the window to set the scrollbar position to the top of swap content after the swap. /// /// The swap style. /// The scroll direction after the swap. @@ -425,22 +432,22 @@ public static class SwapStyleBuilderExtension public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).Scroll(direction); /// - /// Automatically scrolls to the top of the page after a swap. + /// Sets the window 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 after content is swapped. + /// the top after content is swapped immediately and without animation. /// /// The swap style. /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder ScrollTop(this SwapStyle style) => new SwapStyleBuilder(style).ScrollTop(); /// - /// Automatically scrolls to the bottom of the page after a swap. + /// Sets the window 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 after content is swapped. + /// the bottom after content is swapped immediately and without animation. /// /// The swap style. /// The SwapStyleBuilder instance for chaining. @@ -448,7 +455,7 @@ public static class SwapStyleBuilderExtension /// /// Determines whether to ignore the document title in the swap response by appending the modifier - /// ignoreTitle:{ignore}. + /// ignoreTitle:. /// /// /// When set to true, the document title in the swap response will be ignored by adding the modifier @@ -504,10 +511,10 @@ public static class SwapStyleBuilderExtension 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. Allows you - /// to specify that htmx should scroll to the focused element when a request completes + /// unwanted behavior on longer requests when the user has already scrolled away. /// /// /// when true will be focus-scroll:true, otherwise when false @@ -531,31 +538,47 @@ public static class SwapStyleBuilderExtension public static SwapStyleBuilder PreserveFocus(this SwapStyle style, bool scroll = true) => new SwapStyleBuilder(style).PreserveFocus(); /// - /// Positions viewport at top of swap target after swap is completed + /// Smoothly animates the scrollbar position to top of swap target after swap is completed /// /// /// Adds a show modifier show:top /// /// The swap style. - /// The scroll direction after swap. /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOnTop(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(ScrollDirection.Top); + public static SwapStyleBuilder ShowOnTop(this SwapStyle style) => new SwapStyleBuilder(style).ShowOnTop(); /// - /// Positions viewport at top of swap target after swap is completed + /// Smoothly animates the scrollbar position to bottom of swap target after swap is completed /// /// + /// Adds a show modifier with the specified scroll direction. /// Adds a show modifier show:bottom /// /// The swap style. /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder ShowOnBottom(this SwapStyle style) => new SwapStyleBuilder(style).ShowOn(ScrollDirection.Bottom); + /// + /// Smoothly animates the scrollbar position to top or bottom of swap target after swap is completed + /// + /// + /// Adds a show modifier with the specified scroll direction. + /// If is , + /// the modifier show:top is added. Otherwise, show:bottom + /// is added + /// + /// The swap style. + /// The scroll direction after swap. + /// The SwapStyleBuilder instance for chaining. + public static SwapStyleBuilder ShowOn(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(direction); + /// /// 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. + /// 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. /// The CSS selector of the target element. @@ -586,35 +609,37 @@ public static class SwapStyleBuilderExtension public static SwapStyleBuilder ShowOnBottom(this SwapStyle style, string selector) => new SwapStyleBuilder(style).ShowOnBottom(selector); /// - /// Specifies that the swap should show in the window, with an optional scroll direction. + /// 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:{direction}, directing the swap to display the specified element at the bottom of the window. + /// 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 show content at the top of the window. + /// 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. 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. + /// 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 show content at the bottom of the window. + /// 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. This positioning can be used for infinite scrolling, footers or - /// lower-priority information that should not immediately distract from other content. + /// 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. From 6960e6cb58646879246a24052c35c7a82c4b5a89 Mon Sep 17 00:00:00 2001 From: Michael Tanczos Date: Wed, 1 May 2024 12:30:33 -0400 Subject: [PATCH 7/7] feat: Cleaned up code and added missing Scroll selector method Enhanced the SwapStyleBuilder's Scroll and ShowOn methods to allow for more precise control over scrollbar positioning after a content swap. The methods now accept an optional CSS selector parameter, which can be used to specify a target element for scrolling. If provided, the page will scroll to the top or bottom of the selected element after swapping content. Removed redundant code related to smooth animation of scrollbar position. Updated corresponding unit tests accordingly. --- src/Htmxor/Http/SwapStyleBuilder.cs | 177 ++++++------------ .../Http/SwapStyleBuilderTests.cs | 4 +- 2 files changed, 57 insertions(+), 124 deletions(-) diff --git a/src/Htmxor/Http/SwapStyleBuilder.cs b/src/Htmxor/Http/SwapStyleBuilder.cs index 2aeed94..433eb40 100644 --- a/src/Htmxor/Http/SwapStyleBuilder.cs +++ b/src/Htmxor/Http/SwapStyleBuilder.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Specialized; +using System.Numerics; namespace Htmxor.Http; @@ -57,23 +58,25 @@ public SwapStyleBuilder AfterSettleDelay(TimeSpan time) } /// - /// Specifies how to set the current window scrollbar after the swap and appends the modifier scroll:. + /// Specifies how to set the content scrollbar position after the swap and appends the modifier scroll:. /// /// - /// Sets the window scrollbar position after swapping immediately (without animation). For instance, using - /// will add the modifier scroll:top which instructs the window to set the scrollbar position to the top of swap content after the swap. + /// 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) + public SwapStyleBuilder Scroll(ScrollDirection direction, string? selector = null) { switch (direction) { case ScrollDirection.Top: - AddModifier("scroll", "top"); + AddModifier("scroll", selector is null ? "top" : $"{selector}:top"); break; case ScrollDirection.Bottom: - AddModifier("scroll", "bottom"); + AddModifier("scroll", selector is null ? "bottom" : $"{selector}:bottom"); break; } @@ -81,25 +84,29 @@ public SwapStyleBuilder Scroll(ScrollDirection direction) } /// - /// Sets the window scrollbar position to the top of the swapped content after a swap. + /// 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 after content is swapped immediately and without animation. + /// 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() => Scroll(ScrollDirection.Top); + public SwapStyleBuilder ScrollTop(string? selector = null) => Scroll(ScrollDirection.Top, selector); /// - /// Sets the window scrollbar position to the bottom of the swapped content after a swap. + /// 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 after content is swapped immediately and without animation. + /// 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() => Scroll(ScrollDirection.Bottom); - + 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:. @@ -191,51 +198,6 @@ public SwapStyleBuilder ScrollFocus(bool scroll = true) /// The SwapStyleBuilder instance for chaining. public SwapStyleBuilder PreserveFocus() => ScrollFocus(false); - /// - /// Smoothly animates the scrollbar position to top or bottom of swap target after swap is completed - /// - /// - /// Adds a show modifier with the specified scroll direction. - /// If is , - /// the modifier show:top is added. Otherwise, show:bottom - /// is added - /// - /// The scroll direction after swap. - /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOn(ScrollDirection direction) - { - switch (direction) - { - case ScrollDirection.Top: - AddModifier("show", $"top"); - break; - case ScrollDirection.Bottom: - AddModifier("show", $"bottom"); - break; - } - - return this; - } - - /// - /// Smoothly animates the scrollbar position to top of swap target after swap is completed - /// - /// - /// Adds a show modifier show:top - /// - /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnTop() => ShowOn(ScrollDirection.Top); - - /// - /// Smoothly animates the scrollbar position to bottom of swap target after swap is completed - /// - /// - /// Adds a show modifier with the specified scroll direction. - /// Adds a show modifier show:bottom - /// - /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnBottom() => ShowOn(ScrollDirection.Bottom); - /// /// 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. @@ -245,18 +207,18 @@ public SwapStyleBuilder ShowOn(ScrollDirection direction) /// is ".item" and is , the modifier show:.item:top /// is added. /// - /// The CSS selector of the target element. /// The scroll direction after swap. + /// Optional CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction) + public SwapStyleBuilder ShowOn(ScrollDirection direction, string? selector = null) { switch (direction) { case ScrollDirection.Top: - AddModifier("show", $"{selector}:top"); + AddModifier("show", selector is null ? "top" : $"{selector}:top"); break; case ScrollDirection.Bottom: - AddModifier("show", $"{selector}:bottom"); + AddModifier("show", selector is null ? "bottom" : $"{selector}:bottom"); break; } @@ -267,30 +229,30 @@ public SwapStyleBuilder ShowOn(string selector, ScrollDirection direction) /// 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 - /// . + /// This method adds the modifier show::top, smoothly scrolling to the top of the element identified by + /// . /// - /// The CSS selector of the target element. + /// Optional CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnTop(string selector) => ShowOn(selector, ScrollDirection.Top); + 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 - /// . + /// This method adds the modifier show::bottom, smoothly scrolling to the bottom of the element identified by + /// . /// - /// The CSS selector of the target element. + /// Optional CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. - public SwapStyleBuilder ShowOnBottom(string selector) => ShowOn(selector, ScrollDirection.Bottom); + 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 + /// 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. @@ -420,38 +382,44 @@ public static class SwapStyleBuilderExtension public static SwapStyleBuilder AfterSettleDelay(this SwapStyle style, TimeSpan time) => new SwapStyleBuilder(style).AfterSettleDelay(time); /// - /// Specifies how to set the current window scrollbar after the swap and appends the modifier scroll:. + /// Specifies how to set the content scrollbar position after the swap and appends the modifier scroll:. /// /// - /// Sets the window scrollbar position after swapping immediately (without animation). For instance, using - /// will add the modifier scroll:top which instructs the window to set the scrollbar position to the top of swap content after the swap. + /// 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) => new SwapStyleBuilder(style).Scroll(direction); + public static SwapStyleBuilder Scroll(this SwapStyle style, ScrollDirection direction, string? selector) => new SwapStyleBuilder(style).Scroll(direction, selector); /// - /// Sets the window scrollbar position to the top of the swapped content after a swap. + /// 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 after content is swapped immediately and without animation. + /// 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) => new SwapStyleBuilder(style).ScrollTop(); + public static SwapStyleBuilder ScrollTop(this SwapStyle style, string? selector) => new SwapStyleBuilder(style).ScrollTop(selector); /// - /// Sets the window scrollbar position to the bottom of the swapped content after a swap. + /// 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 after content is swapped immediately and without animation. + /// 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) => new SwapStyleBuilder(style).ScrollBottom(); + 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 @@ -537,41 +505,6 @@ public static class SwapStyleBuilderExtension /// The SwapStyleBuilder instance for chaining. public static SwapStyleBuilder PreserveFocus(this SwapStyle style, bool scroll = true) => new SwapStyleBuilder(style).PreserveFocus(); - /// - /// Smoothly animates the scrollbar position to top of swap target after swap is completed - /// - /// - /// Adds a show modifier show:top - /// - /// The swap style. - /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOnTop(this SwapStyle style) => new SwapStyleBuilder(style).ShowOnTop(); - - /// - /// Smoothly animates the scrollbar position to bottom of swap target after swap is completed - /// - /// - /// Adds a show modifier with the specified scroll direction. - /// Adds a show modifier show:bottom - /// - /// The swap style. - /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOnBottom(this SwapStyle style) => new SwapStyleBuilder(style).ShowOn(ScrollDirection.Bottom); - - /// - /// Smoothly animates the scrollbar position to top or bottom of swap target after swap is completed - /// - /// - /// Adds a show modifier with the specified scroll direction. - /// If is , - /// the modifier show:top is added. Otherwise, show:bottom - /// is added - /// - /// The swap style. - /// The scroll direction after swap. - /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOn(this SwapStyle style, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(direction); - /// /// Specifies a CSS selector to dynamically target for the swap operation, with a scroll direction after the swap. /// @@ -581,10 +514,10 @@ public static class SwapStyleBuilderExtension /// is added. /// /// The swap style. - /// The CSS selector of the target element. + /// Optional CSS selector of the target element. /// The scroll direction after swap. /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOn(this SwapStyle style, string selector, ScrollDirection direction) => new SwapStyleBuilder(style).ShowOn(selector, direction); + 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. @@ -593,9 +526,9 @@ public static class SwapStyleBuilderExtension /// 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. - /// The CSS selector of the target element. + /// Optional CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOnTop(this SwapStyle style, string selector) => new SwapStyleBuilder(style).ShowOnTop(selector); + 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. @@ -606,14 +539,14 @@ public static class SwapStyleBuilderExtension /// The swap style. /// The CSS selector of the target element. /// The SwapStyleBuilder instance for chaining. - public static SwapStyleBuilder ShowOnBottom(this SwapStyle style, string selector) => new SwapStyleBuilder(style).ShowOnBottom(selector); + 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 + /// This method adds the modifier show:window:, directing the swap to display the specified /// element at the bottom of the window. /// /// The swap style. diff --git a/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs index 5146512..6f38856 100644 --- a/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs +++ b/test/Htmxor.Tests/Http/SwapStyleBuilderTests.cs @@ -110,7 +110,7 @@ public void SwapStyleBuilder_ShowOn_AddsCorrectSelectorAndDirection() var selector = "#dynamic-area"; // Act - var (_, modifiers) = builder.ShowOn(selector, ScrollDirection.Top).Build(); + var (_, modifiers) = builder.ShowOn(ScrollDirection.Top, selector).Build(); // Assert modifiers.Should().Be("show:#dynamic-area:top"); @@ -168,7 +168,7 @@ public void SwapStyleBuilder_ShowOn_BottomDirection_AddsCorrectModifier() var selector = "#element-id"; // Act - var (_, modifiers) = builder.ShowOn(selector, ScrollDirection.Bottom).Build(); + var (_, modifiers) = builder.ShowOn(ScrollDirection.Bottom, selector).Build(); // Assert modifiers.Should().Be("show:#element-id:bottom");