Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ dotnet_diagnostic.CS8653.severity = error
# CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
dotnet_diagnostic.CS8714.severity = error

# IDE0059: Unnecessary assigment of a value
# IDE0059: Unnecessary assignment of a value
dotnet_diagnostic.IDE0059.severity = none

# IDE0060: Remove unused parameter
Expand Down
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#Ignore thumbnails created by windows
#Ignore thumbnails created by Windows
[Tt]humbs.db

#Ignore files build by Visual Studio
#Ignore files built by Visual Studio
*.obj
*.exe
*.pdb
Expand Down Expand Up @@ -33,7 +33,7 @@ obj/
*.ReSharper.user
[Tt]est[Rr]esult*

#Ignore Rider/Idea files
#Ignore JetBrains IDE files
.idea/

#Ignore npm modules
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,20 @@ Available properties include:
- `HistoryRestoreRequest`
- `Prompt`
- `Request`
- `RequestType`
- `Source`
- `Target`
- `Trigger`
- `TriggerName`

> [!NOTE]
> The request header vocabulary differs between HTMX versions.
> `HX-Trigger`, `HX-Trigger-Name`, and `HX-Prompt` are sent by HTMX 1.x and 2.x only.
> HTMX 4.x does not support `hx-prompt`, identifies the source element with `HX-Source` instead,
> and reports partial or full requests through `HX-Request-Type`.
> In HTMX 4.x, `HX-Target` also carries a `tag#id` value (for example, `div#results`)
> rather than an ID alone.

`HtmxRequestHeaderNames` exposes the corresponding header-name constants for lower-level APIs.

Use `Request.IsHtmxBoosted()` when only boosted navigation matters. An overload also provides access to the strongly typed headers.
Expand Down Expand Up @@ -412,7 +422,8 @@ app.MapHtmxToolkitScript("/assets/htmx-toolkit.js");

> [!IMPORTANT]
> HTMX 1.9.x and 2.x support `HX-Trigger`, `HX-Trigger-After-Swap`, and `HX-Trigger-After-Settle`.
> HTMX 4.x supports only `HX-Trigger`. HtmxToolkit therefore emits events requested for any `HtmxTriggerTiming`
> HTMX 4.x supports only `HX-Trigger`, which fires when the request completes (after the swap whenever one is performed).
> HtmxToolkit therefore emits events requested for any `HtmxTriggerTiming`
> value through that header rather than dropping them. The `Receive` and `AfterSettle` timings cannot be preserved exactly.

### Polling
Expand Down
6 changes: 3 additions & 3 deletions src/Ramstack.HtmxToolkit/Collections/SmallDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ internal Enumerator(SmallDictionary<TKey, TValue> dictionary)
public bool MoveNext()
{
//
// JIT generates redundant mov for inline ++i (pre-increment)
// The JIT generates a redundant mov instruction for inline ++i (pre-increment).
// https://github.com/dotnet/runtime/issues/129532
//

Expand Down Expand Up @@ -758,7 +758,7 @@ internal Enumerator(SmallDictionary<TKey, TValue> dictionary)
public bool MoveNext()
{
//
// JIT generates redundant mov for inline ++i (pre-increment)
// The JIT generates a redundant mov instruction for inline ++i (pre-increment).
// https://github.com/dotnet/runtime/issues/129532
//

Expand Down Expand Up @@ -826,7 +826,7 @@ internal Enumerator(SmallDictionary<TKey, TValue> dictionary)
public bool MoveNext()
{
//
// JIT generates redundant mov for inline ++i (pre-increment)
// The JIT generates a redundant mov instruction for inline ++i (pre-increment).
// https://github.com/dotnet/runtime/issues/129532
//

Expand Down
2 changes: 1 addition & 1 deletion src/Ramstack.HtmxToolkit/Configuration/HtmxConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal HtmxConfig(HtmxTargetVersion version) =>
TargetVersion = version;

/// <summary>
/// Returns this configuration serialized as JSON,
/// Returns this configuration serialized as JSON. The result is
/// cached and reused until the configuration changes.
/// </summary>
/// <returns>
Expand Down
6 changes: 3 additions & 3 deletions src/Ramstack.HtmxToolkit/Configuration/HtmxFetchMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Ramstack.HtmxToolkit.Configuration;
/// <remarks>
/// <para>In HTMX 4.x this is passed as the <c>mode</c> option of the Fetch API.</para>
/// <para>
/// In HTMX 1.x and 2.x (compatibility mode) this maps to the <c>selfRequestsOnly</c>
/// boolean configuration option, where <see cref="SameOrigin" /> yields
/// <see langword="true" /> and any other value yields <see langword="false" />.
/// In HTMX 1.x and 2.x, the equivalent setting is the <c>selfRequestsOnly</c>
/// boolean configuration option, for which <see cref="SameOrigin" /> corresponds to
/// <see langword="true" /> and any other value corresponds to <see langword="false" />.
/// </para>
/// </remarks>
public enum HtmxFetchMode
Expand Down
4 changes: 2 additions & 2 deletions src/Ramstack.HtmxToolkit/Configuration/HtmxV1Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public HtmxBinaryType? WsBinaryType

/// <summary>
/// Gets or sets the selector for elements that HTMX must not process.
/// The HTMX default is <c>[disable-htmx], [data-disable-htmx]</c>.
/// The HTMX default is <c>[hx-disable], [data-hx-disable]</c>.
/// </summary>
public string? DisableSelector
{
Expand Down Expand Up @@ -235,7 +235,7 @@ public int? Timeout

/// <summary>
/// Gets or sets a value indicating whether requests are restricted to the current origin.
/// The HTMX default is <see langword="true" />.
/// The HTMX default is <see langword="false" />.
/// </summary>
public bool? SelfRequestsOnly
{
Expand Down
2 changes: 1 addition & 1 deletion src/Ramstack.HtmxToolkit/Configuration/HtmxV2Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public HtmxBinaryType? WsBinaryType

/// <summary>
/// Gets or sets the selector for elements that HTMX must not process.
/// The HTMX default is <c>[disable-htmx], [data-disable-htmx]</c>.
/// The HTMX default is <c>[hx-disable], [data-hx-disable]</c>.
/// </summary>
public string? DisableSelector
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public sealed class ResponseHandlingConfig
public bool? IgnoreTitle { get; set; }

/// <summary>
/// Gets or sets a CSS selector to use to select content from the response.
/// Gets or sets a CSS selector used to select content from the response.
/// </summary>
public string? Select { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion src/Ramstack.HtmxToolkit/HtmxLocationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public sealed class HtmxLocationOptions
/// <summary>
/// Gets or sets a selector used to select content for out-of-band swaps from the response.
/// </summary>
/// <remarks>Supported only in HTMX 2.x.</remarks>
/// <remarks>Supported in HTMX 2.x and HTMX 4.x.</remarks>
[JsonPropertyName("selectOOB")]
public string? SelectOob { get; set; }

Expand Down
34 changes: 33 additions & 1 deletion src/Ramstack.HtmxToolkit/HtmxRequestHeaderNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public static class HtmxRequestHeaderNames
/// <summary>
/// The <c>HX-Prompt</c> header contains the user's response to an <c>hx-prompt</c>.
/// </summary>
/// <remarks>
/// Supported only in HTMX 1.x and 2.x. HTMX 4.x removed <c>hx-prompt</c>
/// and does not send this header.
/// </remarks>
public const string Prompt = "HX-Prompt";

/// <summary>
Expand All @@ -37,17 +41,45 @@ public static class HtmxRequestHeaderNames
public const string Request = "HX-Request";

/// <summary>
/// The <c>HX-Target</c> header contains the ID of the target element, if present.
/// The <c>HX-Request-Type</c> header indicates whether the request expects a partial
/// or a full page response.
/// </summary>
/// <remarks>
/// Supported only in HTMX 4.x. The value is <c>"partial"</c> for targeted swaps
/// and <c>"full"</c> for body-level or <c>hx-select</c> requests.
/// </remarks>
public const string RequestType = "HX-Request-Type";

/// <summary>
/// The <c>HX-Source</c> header identifies the element that triggered the request.
/// </summary>
/// <remarks>
/// Supported only in HTMX 4.x. The value is in <c>tag#id</c> format, for example <c>button#submit</c>.
/// </remarks>
public const string Source = "HX-Source";

/// <summary>
/// The <c>HX-Target</c> header identifies the target element, if present.
/// </summary>
/// <remarks>
/// <para>In HTMX 1.x and 2.x, the value is the ID of the target element.</para>
/// <para>In HTMX 4.x, the value is in <c>tag#id</c> format, for example <c>div#results</c>.</para>
/// </remarks>
public const string Target = "HX-Target";

/// <summary>
/// The <c>HX-Trigger-Name</c> header contains the name of the triggered element, if present.
/// </summary>
/// <remarks>
/// Supported only in HTMX 1.x and 2.x. HTMX 4.x identifies the source element with <c>HX-Source</c> instead.
/// </remarks>
public const string TriggerName = "HX-Trigger-Name";

/// <summary>
/// The <c>HX-Trigger</c> header contains the ID of the triggered element, if present.
/// </summary>
/// <remarks>
/// Supported only in HTMX 1.x and 2.x. HTMX 4.x identifies the source element with <c>HX-Source</c> instead.
/// </remarks>
public const string Trigger = "HX-Trigger";
}
49 changes: 42 additions & 7 deletions src/Ramstack.HtmxToolkit/HtmxRequestHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ internal HtmxRequestHeaders(HttpRequest request) =>
/// Gets the user's response to an <c>hx-prompt</c> on the client.
/// </summary>
/// <remarks>
/// The header name is <see cref="HtmxRequestHeaderNames.Prompt" />.
/// <para>The header name is <see cref="HtmxRequestHeaderNames.Prompt" />.</para>
/// <para>
/// Supported only in HTMX 1.x and 2.x; HTMX 4.x removed <c>hx-prompt</c>
/// and does not send this header.
/// </para>
/// </remarks>
public string? Prompt => GetString(_headers, HtmxRequestHeaderNames.Prompt);

Expand All @@ -64,26 +68,58 @@ internal HtmxRequestHeaders(HttpRequest request) =>
public bool Request => GetBoolean(_headers, HtmxRequestHeaderNames.Request);

/// <summary>
/// Gets the ID of the target element, if present.
/// Gets the type of the expected response, either a partial or a full page.
/// </summary>
/// <remarks>
/// The header name is <see cref="HtmxRequestHeaderNames.Target" />.
/// <para>The header name is <see cref="HtmxRequestHeaderNames.RequestType" />.</para>
/// <para>
/// Supported only in HTMX 4.x. The value is <c>"partial"</c> for targeted swaps
/// and <c>"full"</c> for body-level or <c>hx-select</c> requests.
/// </para>
/// </remarks>
public string? RequestType => GetString(_headers, HtmxRequestHeaderNames.RequestType);

/// <summary>
/// Gets the identifier of the element that triggered the request.
/// </summary>
/// <remarks>
/// <para>The header name is <see cref="HtmxRequestHeaderNames.Source" />.</para>
/// <para>
/// Supported only in HTMX 4.x. The value is in <c>tag#id</c> format, for example <c>button#submit</c>.
/// </para>
/// </remarks>
public string? Source => GetString(_headers, HtmxRequestHeaderNames.Source);

/// <summary>
/// Gets the identifier of the target element, if present.
/// </summary>
/// <remarks>
/// <para>The header name is <see cref="HtmxRequestHeaderNames.Target" />.</para>
/// <para>In HTMX 1.x and 2.x, the value is the ID of the target element.</para>
/// <para>In HTMX 4.x, the value is in <c>tag#id</c> format, for example <c>div#results</c>.</para>
/// </remarks>
public string? Target => GetString(_headers, HtmxRequestHeaderNames.Target);

/// <summary>
/// Gets the name of the triggered element, if present.
/// </summary>
/// <remarks>
/// The header name is <see cref="HtmxRequestHeaderNames.TriggerName" />.
/// <para>The header name is <see cref="HtmxRequestHeaderNames.TriggerName" />.</para>
/// <para>
/// Supported only in HTMX 1.x and 2.x; HTMX 4.x identifies the source element
/// with <c>HX-Source</c> instead.
/// </para>
/// </remarks>
public string? TriggerName => GetString(_headers, HtmxRequestHeaderNames.TriggerName);

/// <summary>
/// Gets the ID of the triggered element, if present.
/// </summary>
/// <remarks>
/// The header name is <see cref="HtmxRequestHeaderNames.Trigger" />.
/// <para>The header name is <see cref="HtmxRequestHeaderNames.Trigger" />.</para>
/// <para>
/// Supported only in HTMX 1.x and 2.x; HTMX 4.x identifies the source element with <c>HX-Source</c> instead.
/// </para>
/// </remarks>
public string? Trigger => GetString(_headers, HtmxRequestHeaderNames.Trigger);

Expand All @@ -93,8 +129,7 @@ internal HtmxRequestHeaders(HttpRequest request) =>
/// <param name="dictionary">The header collection to inspect.</param>
/// <param name="key">The name of the header.</param>
/// <returns>
/// <see langword="true" /> if the header value is <c>"true"</c>;
/// otherwise, <see langword="false" />.
/// <see langword="true" /> if the header value is <c>"true"</c>; otherwise, <see langword="false" />.
/// </returns>
private static bool GetBoolean(IHeaderDictionary dictionary, string key) =>
dictionary.TryGetValue(key, out var value) && value[0] == "true";
Expand Down
24 changes: 12 additions & 12 deletions src/Ramstack.HtmxToolkit/HtmxResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static HtmxResponse LocationImpl(HtmxResponse response, string path, HtmxLocatio
/// The possible values for this header are:
/// <list type="bullet">
/// <item>
/// A URL to be pushed into the location bar. This may be relative or absolute,
/// A relative or same-origin absolute URL to be pushed into the location bar,
/// as supported by <see href="https://developer.mozilla.org/en-US/docs/Web/API/History/pushState">history.pushState()</see>.
/// </item>
/// <item>
Expand Down Expand Up @@ -202,7 +202,7 @@ public HtmxResponse Reselect(string value) =>
/// </returns>
/// <remarks>
/// In HTMX 4.x, every <see cref="HtmxTriggerTiming" /> value is emitted through
/// <c>HX-Trigger</c> and runs after the swap.
/// <c>HX-Trigger</c> and runs when the request completes (after the swap whenever one is performed).
/// See <see href="https://github.com/bigskysoftware/htmx/pull/3900">PR #3900</see>.
/// </remarks>
public HtmxResponse TriggerEvent(string eventName, HtmxTriggerTiming trigger = HtmxTriggerTiming.Receive) =>
Expand All @@ -212,17 +212,17 @@ public HtmxResponse TriggerEvent(string eventName, HtmxTriggerTiming trigger = H
/// Adds a client-side event and its detail to the response header selected by
/// <paramref name="timing" />.
/// </summary>
/// <remarks>
/// In HTMX 4.x, every <see cref="HtmxTriggerTiming" /> value is emitted through <c>HX-Trigger</c>
/// and runs when the request completes (after the swap whenever one is performed).
/// See <see href="https://github.com/bigskysoftware/htmx/pull/3900">PR #3900</see>.
/// </remarks>
/// <param name="eventName">The event name to trigger.</param>
/// <param name="detail">The event detail.</param>
/// <param name="timing">The event timing. Defaults to <see cref="HtmxTriggerTiming.Receive" />.</param>
/// <returns>
/// The current <see cref="HtmxResponse" /> instance.
/// </returns>
/// <remarks>
/// In HTMX 4.x, every <see cref="HtmxTriggerTiming" /> value is emitted through
/// <c>HX-Trigger</c> and runs after the swap.
/// See <see href="https://github.com/bigskysoftware/htmx/pull/3900">PR #3900</see>.
/// </remarks>
public HtmxResponse TriggerEvent(string eventName, object detail, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive)
{
return TriggerEventImpl(this, eventName, detail, timing);
Expand All @@ -234,16 +234,16 @@ static HtmxResponse TriggerEventImpl(HtmxResponse response, string eventName, ob
/// <summary>
/// Adds client-side events to the response header selected by <paramref name="timing" />.
/// </summary>
/// <remarks>
/// In HTMX 4.x, every <see cref="HtmxTriggerTiming" /> value is emitted through <c>HX-Trigger</c>
/// and runs when the request completes (after the swap whenever one is performed).
/// See <see href="https://github.com/bigskysoftware/htmx/pull/3900">PR #3900</see>.
/// </remarks>
/// <param name="events">The event names and their associated details.</param>
/// <param name="timing">The event timing. Defaults to <see cref="HtmxTriggerTiming.Receive" />.</param>
/// <returns>
/// The current <see cref="HtmxResponse" /> instance.
/// </returns>
/// <remarks>
/// In HTMX 4.x, every <see cref="HtmxTriggerTiming" /> value is emitted through
/// <c>HX-Trigger</c> and runs after the swap.
/// See <see href="https://github.com/bigskysoftware/htmx/pull/3900">PR #3900</see>.
/// </remarks>
public HtmxResponse TriggerEvents(IReadOnlyDictionary<string, object> events, HtmxTriggerTiming timing = HtmxTriggerTiming.Receive) =>
AddEvents(this, events, timing);

Expand Down
11 changes: 7 additions & 4 deletions src/Ramstack.HtmxToolkit/HtmxResponseHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public string Reselect
/// </para>
/// <para>
/// HTMX 1.x and 2.x trigger these events when the response is received,
/// whereas HTMX 4.x triggers them after the swap.
/// whereas HTMX 4.x triggers them when the request completes
/// (after the swap whenever one is performed).
/// </para>
/// </remarks>
[MaybeNull]
Expand All @@ -147,7 +148,8 @@ public IReadOnlyDictionary<string, object> Trigger
/// </para>
/// <para>
/// In HTMX 4.x, assigned events are accumulated in <see cref="Trigger" />
/// and emitted through <c>HX-Trigger</c> after the swap.
/// and emitted through <c>HX-Trigger</c> when the request completes
/// (after the swap whenever one is performed).
/// </para>
/// </remarks>
[MaybeNull]
Expand All @@ -168,8 +170,9 @@ public IReadOnlyDictionary<string, object> TriggerAfterSwap
/// </para>
/// <para>
/// In HTMX 4.x, assigned events are accumulated in <see cref="Trigger" />
/// and emitted through <c>HX-Trigger</c> after the swap; the requested
/// after-settle timing cannot be preserved.
/// and emitted through <c>HX-Trigger</c> when the request completes
/// (after the swap whenever one is performed); the requested after-settle timing
/// cannot be preserved.
/// </para>
/// </remarks>
[MaybeNull]
Expand Down
Loading