diff --git a/README.md b/README.md index 1cd4ea6..d14aced 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ HtmxToolkit integrates [HTMX](https://htmx.org/) with ASP.NET Core. It provides strongly typed APIs for request and response headers, MVC action filters, Razor Tag Helpers, application-wide HTMX configuration, and antiforgery support. -- The package targets .NET 6 and can be used by applications running on .NET 6 or later. -- It supports HTMX 1.9.x, HTMX 2.x, and HTMX 4.x. HTMX 2.x is selected by default. +- Supports .NET 6 or later. +- Supports HTMX 1.9.x, HTMX 2.x, and HTMX 4.x. HTMX 2.x is selected by default. + +See the [documentation](docs/articles/index.md) for full guides and recipes. ## Features @@ -22,14 +24,11 @@ MVC action filters, Razor Tag Helpers, application-wide HTMX configuration, and ## Designed for Low Overhead -HtmxToolkit is designed to minimize HTMX integration overhead in the application's request-processing path: +`HtmxRequestHeaders` and `HtmxResponseHeaders` are `readonly` structs, each containing a single reference. +This avoids allocating wrapper objects and allows the JIT to optimize away the wrapper overhead in inlined code. -- `HtmxRequestHeaders` and `HtmxResponseHeaders` are `readonly` structs, each containing a single reference. - In normal use, they incur no wrapper allocations while preserving a strongly typed API. -- Version-specific HTMX configuration is serialized only when the configuration changes; the resulting JSON is cached and reused across requests. -- Known JSON shapes use source-generated `System.Text.Json` metadata, avoiding reflection-based metadata discovery at runtime. - Applications can pass `JsonTypeInfo` to `TriggerEvent` to serialize their event details without reflection. -- Work is skipped for non-HTMX requests, and overloads that accept state allow callers to use static callbacks and avoid closure allocations. +Version-specific configuration JSON is cached and reused until the configuration changes. +Known JSON shapes use source-generated `System.Text.Json` metadata, avoiding reflection-based metadata discovery at runtime. ## Installation @@ -75,8 +74,10 @@ app.MapStaticAssets(); app.MapRazorPages().WithStaticAssets(); ``` -For MVC, apply `.WithStaticAssets()` to the controller endpoint builder instead; a hybrid Razor Pages and MVC -application applies it to each endpoint set. On ASP.NET Core 6–8, enable static files: +> [!NOTE] +> For MVC, apply `.WithStaticAssets()` to each controller endpoint builder that renders views + +On ASP.NET Core 6–8, enable static files: ```csharp app.UseStaticFiles(); @@ -86,13 +87,15 @@ The NuGet package includes the toolkit script as a static web asset. Load it aft ```html - + ``` You can now generate an HTMX URL from ASP.NET Core route information: ```html - - - - - + ... ``` -The `/profile/morph` endpoint should return the replacement root, such as `
Updated profile
`. - -The toolkit script does not bundle HTMX or Idiomorph and must be loaded after HTMX. Idiomorph remains an optional dependency -and may be loaded before or after the toolkit script because the adapter resolves it when each morph swap runs. Do not enable -the extension with HTMX 4.x, which handles these swap styles natively. - -If Idiomorph is unavailable, the adapter logs a warning and falls back from `innerMorph` to `innerHTML` and from `outerMorph` -to `outerHTML`. With HTMX 1.9.x and 2.x, `outerSync` falls back to synchronizing the target's attributes and then replacing -its children using `innerHTML`. - -`HtmxSwap.TextContent` is also handled by the `ramstack-morph` extension and does not require Idiomorph. It is supported natively -by HTMX 2.x and 4.x; only HTMX 1.9.x needs the extension. +Without Idiomorph, the extension falls back to HTML replacement. +See [Morph swaps](docs/articles/version-compatibility.md#morph-swaps) for script setup, supported styles, and fallback behavior. ## Running Locally -Run the following commands from the repository root. - ### Demo The [`samples/Ramstack.HtmxToolkit.Demo`](samples/Ramstack.HtmxToolkit.Demo) project demonstrates request detection, response headers and events, MVC attributes, Tag Helpers, polling, boosted navigation, and antiforgery integration. -Run it with: +Run it from the repository root: ```console dotnet run --project samples/Ramstack.HtmxToolkit.Demo @@ -544,15 +405,7 @@ The application is available at and after DocFX finishes the initial build. See -[`docs/README.md`](docs/README.md) for standalone builds and information about API examples. +See [Building the documentation locally](docs/README.md) for build and preview commands. ## Contributing @@ -563,6 +416,14 @@ dotnet build dotnet test ``` +## Supported versions + +| | Version | +|------|--------------------| +| .NET | 6, 7, 8, 9, 10, 11 | +| HTMX | 1.9.x, 2.x, 4.x | + + ## License HtmxToolkit is available under the [MIT License](LICENSE). diff --git a/docs/articles/configuration-v1.md b/docs/articles/configuration-v1.md index 79091e6..13c2376 100644 --- a/docs/articles/configuration-v1.md +++ b/docs/articles/configuration-v1.md @@ -69,5 +69,7 @@ the compatibility extension on HTMX 1.9.x. ``` +See [Morph swaps](version-compatibility.md#morph-swaps) for script setup and fallback behavior. + Use the [API reference](../api/Ramstack.HtmxToolkit.Configuration.HtmxV1Config.yml) for property types and declared defaults, and review [Version compatibility](version-compatibility.md) before migrating. diff --git a/docs/articles/configuration-v2.md b/docs/articles/configuration-v2.md index 0a3a074..1f2eed8 100644 --- a/docs/articles/configuration-v2.md +++ b/docs/articles/configuration-v2.md @@ -83,4 +83,6 @@ otherwise the extension uses its documented fallback behavior. ``` +See [Morph swaps](version-compatibility.md#morph-swaps) for script setup and fallback behavior. + Use the [API reference](../api/Ramstack.HtmxToolkit.Configuration.HtmxV2Config.yml) for property types and declared defaults. diff --git a/docs/articles/configuration-v4.md b/docs/articles/configuration-v4.md index 6ea1039..e4efb2c 100644 --- a/docs/articles/configuration-v4.md +++ b/docs/articles/configuration-v4.md @@ -109,7 +109,8 @@ declarations can still use `*-append` to merge with inherited objects. ## Prevent swaps for status codes -`NoSwap` accepts exact status codes and wildcard patterns: +HTMX 4.x replaces `responseHandling` with `noSwap` and swaps `4xx` and `5xx` responses by default. +To restore the default HTMX 2.x behavior for those errors, set `NoSwap` using exact status codes and wildcard patterns: ```csharp config.NoSwap = ["204", "304", "4xx", "5xx"]; @@ -118,6 +119,9 @@ config.NoSwap = ["204", "304", "4xx", "5xx"]; The HTMX default contains 204 and 304. Assigning the property replaces that list, so preserve those entries if the application still relies on their default behavior. +This policy also prevents `422` validation responses from swapping. Omit or narrow the `4xx` pattern if those +responses should continue to update the page. See the [HTMX 4.x migration guide](https://four.htmx.org/docs/#migrating-from-htmx-2x-to-4x). + ## Morphing HTMX 4 natively supports `InnerMorph`, `OuterMorph`, and `OuterSync`. The morph settings control matching and exclusions: diff --git a/docs/articles/recipes.md b/docs/articles/recipes.md index c5052b7..9358c70 100644 --- a/docs/articles/recipes.md +++ b/docs/articles/recipes.md @@ -74,7 +74,8 @@ This keeps the server response in control while allowing independently targeted ## Poll a background operation -Return markup that contains the next poll while work is incomplete: +For polling that works with every supported HTMX version, return the polling element itself and replace it with +`outerHTML`. Each replacement element schedules the next request with `load delay:1s` while work is incomplete: ```html @model ProgressState @@ -89,7 +90,7 @@ else hx-page="/Jobs/Status" hx-page-handler="Progress" hx-route-progress="@Model.Percent" - hx-trigger="every 500ms" + hx-trigger="load delay:1s" hx-target="this" hx-swap="outerHTML"> @Model.Percent% @@ -105,7 +106,8 @@ public IActionResult OnGetProgress(int progress) } ``` -Polling stops naturally because completed markup no longer contains `hx-trigger="every ..."`. +Polling stops when the server returns the completed element without the request and trigger attributes. +This pattern does not depend on status code `286`, whose behavior [differs between HTMX versions](version-compatibility.md#polling). ## Handle boosted navigation progressively diff --git a/docs/articles/version-compatibility.md b/docs/articles/version-compatibility.md index 8b3206f..3a80ef2 100644 --- a/docs/articles/version-compatibility.md +++ b/docs/articles/version-compatibility.md @@ -16,6 +16,7 @@ and the companion script generate the matching contract. | Prompt result | `HX-Prompt` | Not supported | `Prompt` | | Expected response | Not reported | `HX-Request-Type`: `partial` or `full` | `RequestType` | +In HTMX 4.x, a target value such as `div#results` includes the tag name as well as the ID. Code shared across versions should tolerate null for version-specific properties. ```csharp @@ -80,6 +81,49 @@ HTMX 1.x and 2.x distinguish `HX-Trigger`, `HX-Trigger-After-Swap`, and `HX-Trig HTMX 4.x emits Toolkit events through `HX-Trigger` when the request completes. An `AfterSettle` timing therefore cannot retain its separate V1/V2 timing under V4. +## Polling + +Status code `286` stops polling in HTMX 1.9.x and 2.x. HTMX 4.x treats it as a regular successful response. +For a cross-version approach, return the polling element with `hx-trigger="load delay:1s"` and `hx-swap="outerHTML"` +to schedule the next request, then omit the request and trigger attributes when polling should stop. +See [Poll a background operation](recipes.md#poll-a-background-operation) for a complete example. + +## Morph swaps + +`HtmxSwap.InnerMorph`, `OuterMorph`, and `OuterSync` use native swap styles in HTMX 4.x. +Do not enable the `ramstack-morph` extension with that version. + +With HTMX 1.9.x or 2.x, enable the extension supplied by the Toolkit script. Load the optional Idiomorph library +before the first morph swap to preserve morphing behavior: + +```html + +
Current profile
+ + + + + + + +``` + +The `/profile/morph` endpoint should return the replacement root, such as `
Updated profile
`. + +The Toolkit script does not bundle HTMX or Idiomorph and must be loaded after HTMX. Idiomorph may be loaded +before or after the Toolkit script because the adapter resolves it when each morph swap runs. + +If Idiomorph is unavailable, the adapter logs a warning and falls back from `innerMorph` to `innerHTML` and from +`outerMorph` to `outerHTML`. With HTMX 1.9.x and 2.x, `outerSync` falls back to synchronizing the target's attributes +and then replacing its children using `innerHTML`. + +`HtmxSwap.TextContent` is also handled by the `ramstack-morph` extension and does not require Idiomorph. +It is supported natively by HTMX 2.x and 4.x; only HTMX 1.9.x needs the extension. + ## Migration checklist 1. Update the HTMX client script.