Sync attrs/hdrs with upstream

This commit is contained in:
2026-06-13 17:00:19 -04:00
parent d4a7e0c9ce
commit 62af36e833
6 changed files with 241 additions and 85 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ module HxSwap =
/// <summary>Morph the outer HTML of the target to the new content</summary> /// <summary>Morph the outer HTML of the target to the new content</summary>
[<Literal>] [<Literal>]
let OuterMorph = "innerMorph" let OuterMorph = "outerMorph"
/// <summary>Replace the text content of the target without parsing the response as HTML</summary> /// <summary>Replace the text content of the target without parsing the response as HTML</summary>
[<Literal>] [<Literal>]
+42 -5
View File
@@ -4,6 +4,14 @@ open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Primitives open Microsoft.Extensions.Primitives
open System open System
/// <summary>The request types which may be set in the <c>HX-Request</c> header</summary>
type HxRequestTypes =
/// <summary>A request targeting the <c>body</c> tag or using an <c>hx-select</c> attribute</summary>
| HxFullRequest
/// <summary>A request for partial content</summary>
| HxPartialRequest
/// Determine if the given header is present /// Determine if the given header is present
let private hdr (headers : IHeaderDictionary) hdr = let private hdr (headers : IHeaderDictionary) hdr =
match headers[hdr] with it when it = StringValues.Empty -> None | it -> Some it[0] match headers[hdr] with it when it = StringValues.Empty -> None | it -> Some it[0]
@@ -32,6 +40,16 @@ type IHeaderDictionary with
member this.HxRequest member this.HxRequest
with get () = hdr this "HX-Request" |> Option.map bool.Parse with get () = hdr this "HX-Request" |> Option.map bool.Parse
/// <summary>The request type sent by htmx</summary>
/// <seealso cref="HxRequestTypes" />
member this.HxRequestType
with get () =
match hdr this "HX-Request-Type" with
| Some typ when typ = "full" -> Some HxFullRequest
| Some typ when typ = "partial" -> Some HxPartialRequest
| Some _ -> None
| None -> None
/// <summary>The tag name (fst) and <c>id</c> attribute (snd) of the element triggering this request</summary> /// <summary>The tag name (fst) and <c>id</c> attribute (snd) of the element triggering this request</summary>
member this.HxSource member this.HxSource
with get () = with get () =
@@ -45,8 +63,23 @@ type IHeaderDictionary with
| None -> None | None -> None
/// <summary>The <c>id</c> attribute of the target element if it exists</summary> /// <summary>The <c>id</c> attribute of the target element if it exists</summary>
/// <remarks>
/// In v4, this changed to tag name (fst) and <c>id</c> (snd); to resolve build errors, and restore the prior
/// behavior of <c>[id] option</c>, replace
/// <code>ctx.HxTarget</code>
/// with
/// <code>ctx.HxTarget |> Option.iter snd</code>
/// </remarks>
member this.HxTarget member this.HxTarget
with get () = hdr this "HX-Target" with get () =
match hdr this "HX-Target" with
| Some src ->
let parts = src.Split "#"
if parts.Length = 1 then
Some (parts[0], None)
else
Some (parts[0], if parts[1] <> "" then Some parts[1] else None)
| None -> None
/// <summary>The <c>id</c> attribute of the triggered element if it exists</summary> /// <summary>The <c>id</c> attribute of the triggered element if it exists</summary>
[<Obsolete "HX-Trigger is removed in v4; use the second item of HX-Source">] [<Obsolete "HX-Trigger is removed in v4; use the second item of HX-Source">]
@@ -162,29 +195,33 @@ module Handlers =
/// <param name="evt">The call to the event that should be triggered</param> /// <param name="evt">The call to the event that should be triggered</param>
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set</returns> /// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set</returns>
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso> /// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
[<Obsolete "Removed in v4; use withHxTrigger">]
let withHxTriggerAfterSettle (evt: string) : HttpHandler = let withHxTriggerAfterSettle (evt: string) : HttpHandler =
setHttpHeader "HX-Trigger-After-Settle" evt setHttpHeader "HX-Trigger" evt
/// <summary>Allows you to trigger multiple client side events after changes have settled</summary> /// <summary>Allows you to trigger multiple client side events after changes have settled</summary>
/// <param name="evts">The calls to events that should be triggered</param> /// <param name="evts">The calls to events that should be triggered</param>
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set for all given events</returns> /// <returns>An HTTP handler with the <c>HX-Trigger-After-Settle</c> header set for all given events</returns>
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso> /// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
[<Obsolete "Removed in v4; use withHxTrigger">]
let withHxTriggerManyAfterSettle evts : HttpHandler = let withHxTriggerManyAfterSettle evts : HttpHandler =
toJson evts |> setHttpHeader "HX-Trigger-After-Settle" toJson evts |> setHttpHeader "HX-Trigger"
/// <summary>Allows you to trigger a single client side event after DOM swapping occurs</summary> /// <summary>Allows you to trigger a single client side event after DOM swapping occurs</summary>
/// <param name="evt">The call to the event that should be triggered</param> /// <param name="evt">The call to the event that should be triggered</param>
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set</returns> /// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set</returns>
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso> /// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
[<Obsolete "Removed in v4; use withHxTrigger">]
let withHxTriggerAfterSwap (evt: string) : HttpHandler = let withHxTriggerAfterSwap (evt: string) : HttpHandler =
setHttpHeader "HX-Trigger-After-Swap" evt setHttpHeader "HX-Trigger" evt
/// <summary>Allows you to trigger multiple client side events after DOM swapping occurs</summary> /// <summary>Allows you to trigger multiple client side events after DOM swapping occurs</summary>
/// <param name="evts">The calls to events that should be triggered</param> /// <param name="evts">The calls to events that should be triggered</param>
/// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set for all given events</returns> /// <returns>An HTTP handler with the <c>HX-Trigger-After-Swap</c> header set for all given events</returns>
/// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso> /// <seealso href="https://htmx.org/headers/hx-trigger/">Documentation</seealso>
[<Obsolete "Removed in v4; use withHxTrigger">]
let withHxTriggerManyAfterSwap evts : HttpHandler = let withHxTriggerManyAfterSwap evts : HttpHandler =
toJson evts |> setHttpHeader "HX-Trigger-After-Swap" toJson evts |> setHttpHeader "HX-Trigger"
/// <summary>Load the package-provided version of the htmx script</summary> /// <summary>Load the package-provided version of the htmx script</summary>
+1 -1
View File
@@ -22,7 +22,7 @@ let swap =
Expect.equal HxSwap.InnerMorph "innerMorph" "Inner Morph swap value incorrect" Expect.equal HxSwap.InnerMorph "innerMorph" "Inner Morph swap value incorrect"
} }
test "OuterMorph is correct" { test "OuterMorph is correct" {
Expect.equal HxSwap.OuterMorph "innerMorph" "Outer Morph swap value incorrect" Expect.equal HxSwap.OuterMorph "outerMorph" "Outer Morph swap value incorrect"
} }
test "TextContent is correct" { test "TextContent is correct" {
Expect.equal HxSwap.TextContent "textContent" "Text Content swap value incorrect" Expect.equal HxSwap.TextContent "textContent" "Text Content swap value incorrect"
+94 -43
View File
@@ -96,6 +96,36 @@ let dictExtensions =
Expect.isFalse ctx.Request.Headers.HxRequest.Value "The header should have been false" Expect.isFalse ctx.Request.Headers.HxRequest.Value "The header should have been false"
} }
] ]
testList "HxRequestType" [
test "succeeds when the header is not present" {
let ctx = Substitute.For<HttpContext>()
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxRequestType "There should not have been a header returned"
}
test "succeeds when the header is invalid" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Request-Type", "relaxed")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isNone ctx.Request.Headers.HxRequestType "There should not have been a header returned"
}
test "succeeds for a full request" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Request-Type", "full")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxRequestType "There have been a header returned"
Expect.equal ctx.Request.Headers.HxRequestType.Value HxFullRequest "The header value is incorrect"
}
test "succeeds for a partial request" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Request-Type", "partial")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxRequestType "There have been a header returned"
Expect.equal ctx.Request.Headers.HxRequestType.Value HxPartialRequest "The header value is incorrect"
}
]
testList "HxSource" [ testList "HxSource" [
test "succeeds when the header is not present" { test "succeeds when the header is not present" {
let ctx = Substitute.For<HttpContext>() let ctx = Substitute.For<HttpContext>()
@@ -140,13 +170,36 @@ let dictExtensions =
ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore
Expect.isNone ctx.Request.Headers.HxTarget "There should not have been a header returned" Expect.isNone ctx.Request.Headers.HxTarget "There should not have been a header returned"
} }
test "succeeds when the header is present" { test "succeeds when the header is present and both parts exist" {
let ctx = Substitute.For<HttpContext>() let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary() let dic = HeaderDictionary()
dic.Add("HX-Target", "#leItem") dic.Add("HX-Target", "div#leItem")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
Expect.isSome ctx.Request.Headers.HxTarget "There should be a header present" let hdr = ctx.Request.Headers.HxTarget
Expect.equal ctx.Request.Headers.HxTarget.Value "#leItem" "The header value was incorrect" Expect.isSome hdr "There should be a header present"
Expect.equal (fst hdr.Value) "div" "The target tag was incorrect"
Expect.isSome (snd hdr.Value) "There should be a target ID present"
Expect.equal (snd hdr.Value).Value "leItem" "The header value was incorrect"
}
test "succeeds when the header is present and ID is blank" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Target", "span#")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
let hdr = ctx.Request.Headers.HxTarget
Expect.isSome hdr "There should be a header present"
Expect.equal (fst hdr.Value) "span" "The target tag was incorrect"
Expect.isNone (snd hdr.Value) "There should not be a target ID present"
}
test "succeeds when the header is present and ID is missing" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
dic.Add("HX-Target", "aside")
ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore
let hdr = ctx.Request.Headers.HxTarget
Expect.isSome hdr "There should be a header present"
Expect.equal (fst hdr.Value) "aside" "The target tag was incorrect"
Expect.isNone (snd hdr.Value) "There should not be a target ID present"
} }
] ]
] ]
@@ -307,44 +360,6 @@ let handlers =
Expect.equal Expect.equal
dic["HX-Trigger"].[0] """{ "blah": "foo", "bleh": "bar" }""" "The HX-Trigger value was incorrect" dic["HX-Trigger"].[0] """{ "blah": "foo", "bleh": "bar" }""" "The HX-Trigger value was incorrect"
} }
testTask "withHxTriggerAfterSettle succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerAfterSettle "byTheWay" next ctx
Expect.isTrue
(dic.ContainsKey "HX-Trigger-After-Settle") "The HX-Trigger-After-Settle header should be present"
Expect.equal dic["HX-Trigger-After-Settle"].[0] "byTheWay" "The HX-Trigger-After-Settle value was incorrect"
}
testTask "withHxTriggerManyAfterSettle succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx
Expect.isTrue
(dic.ContainsKey "HX-Trigger-After-Settle") "The HX-Trigger-After-Settle header should be present"
Expect.equal
dic["HX-Trigger-After-Settle"].[0] """{ "oof": "ouch", "hmm": "uh" }"""
"The HX-Trigger-After-Settle value was incorrect"
}
testTask "withHxTriggerAfterSwap succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerAfterSwap "justASec" next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger-After-Swap") "The HX-Trigger-After-Swap header should be present"
Expect.equal dic["HX-Trigger-After-Swap"].[0] "justASec" "The HX-Trigger-After-Swap value was incorrect"
}
testTask "withHxTriggerManyAfterSwap succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerManyAfterSwap [ "this", "1"; "that", "2" ] next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger-After-Swap") "The HX-Trigger-After-Swap header should be present"
Expect.equal
dic["HX-Trigger-After-Swap"].[0] """{ "this": "1", "that": "2" }"""
"The HX-Trigger-After-Swap value was incorrect"
}
] ]
/// Tests for the HtmxScript module /// Tests for the HtmxScript module
@@ -408,5 +423,41 @@ let dictExtensionsObs =
] ]
] ]
let handlerObs =
testList "Handler Tests (Obsolete)" [
testTask "withHxTriggerAfterSettle succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerAfterSettle "byTheWay" next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
Expect.equal dic["HX-Trigger"].[0] "byTheWay" "The HX-Trigger value was incorrect"
}
testTask "withHxTriggerManyAfterSettle succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
Expect.equal dic["HX-Trigger"].[0] """{ "oof": "ouch", "hmm": "uh" }""" "The HX-Trigger value was incorrect"
}
testTask "withHxTriggerAfterSwap succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerAfterSwap "justASec" next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
Expect.equal dic["HX-Trigger"].[0] "justASec" "The HX-Trigger value was incorrect"
}
testTask "withHxTriggerManyAfterSwap succeeds" {
let ctx = Substitute.For<HttpContext>()
let dic = HeaderDictionary()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
let! _ = withHxTriggerManyAfterSwap [ "this", "1"; "that", "2" ] next ctx
Expect.isTrue (dic.ContainsKey "HX-Trigger") "The HX-Trigger header should be present"
Expect.equal dic["HX-Trigger"].[0] """{ "this": "1", "that": "2" }""" "The HX-Trigger value was incorrect"
}
]
/// All tests for this module /// All tests for this module
let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script; dictExtensionsObs ] let allTests = testList "Htmx" [ dictExtensions; reqExtensions; handlers; script; dictExtensionsObs; handlerObs ]
+53 -21
View File
@@ -1,6 +1,7 @@
module ViewEngine module ViewEngine
open Expecto open Expecto
open Giraffe.Htmx
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Htmx open Giraffe.ViewEngine.Htmx
@@ -129,6 +130,14 @@ let hxEvent =
Expect.equal (AfterRequest.ToHxOnString()) "after:request" "AfterRequest hx-on event name not correct" Expect.equal (AfterRequest.ToHxOnString()) "after:request" "AfterRequest hx-on event name not correct"
} }
] ]
testList "AfterSettle" [
test "ToString succeeds" {
Expect.equal (string AfterSettle) "afterSettle" "AfterSettle event name not correct"
}
test "ToHxOnString succeeds" {
Expect.equal (AfterSettle.ToHxOnString()) "after:settle" "AfterSettle hx-on event name not correct"
}
]
testList "AfterSseMessage" [ testList "AfterSseMessage" [
test "ToString succeeds" { test "ToString succeeds" {
Expect.equal (string AfterSseMessage) "afterSseMessage" "AfterSseMessage event name not correct" Expect.equal (string AfterSseMessage) "afterSseMessage" "AfterSseMessage event name not correct"
@@ -184,6 +193,15 @@ let hxEvent =
Expect.equal (BeforeInit.ToHxOnString()) "before:init" "BeforeInit hx-on event name not correct" Expect.equal (BeforeInit.ToHxOnString()) "before:init" "BeforeInit hx-on event name not correct"
} }
] ]
testList "BeforeProcess" [
test "ToString succeeds" {
Expect.equal (string BeforeProcess) "beforeProcess" "BeforeProcess event name not correct"
}
test "ToHxOnString succeeds" {
Expect.equal
(BeforeProcess.ToHxOnString()) "before:process" "BeforeProcess hx-on event name not correct"
}
]
testList "BeforeRestoreHistory" [ testList "BeforeRestoreHistory" [
test "ToString succeeds" { test "ToString succeeds" {
Expect.equal Expect.equal
@@ -205,6 +223,15 @@ let hxEvent =
(BeforeRequest.ToHxOnString()) "before:request" "BeforeRequest hx-on event name not correct" (BeforeRequest.ToHxOnString()) "before:request" "BeforeRequest hx-on event name not correct"
} }
] ]
testList "BeforeResponse" [
test "ToString succeeds" {
Expect.equal (string BeforeResponse) "beforeResponse" "BeforeResponse event name not correct"
}
test "ToHxOnString succeeds" {
Expect.equal
(BeforeResponse.ToHxOnString()) "before:response" "BeforeResponse hx-on event name not correct"
}
]
testList "BeforeSseMessage" [ testList "BeforeSseMessage" [
test "ToString succeeds" { test "ToString succeeds" {
Expect.equal (string BeforeSseMessage) "beforeSseMessage" "BeforeSseMessage event name not correct" Expect.equal (string BeforeSseMessage) "beforeSseMessage" "BeforeSseMessage event name not correct"
@@ -279,6 +306,15 @@ let hxEvent =
(FinallyRequest.ToHxOnString()) "finally:request" "FinallyRequest hx-on event name not correct" (FinallyRequest.ToHxOnString()) "finally:request" "FinallyRequest hx-on event name not correct"
} }
] ]
testList "ResponseError" [
test "ToString succeeds" {
Expect.equal (string ResponseError) "responseError" "ResponseError event name not correct"
}
test "ToHxOnString succeeds" {
Expect.equal
(ResponseError.ToHxOnString()) "response:error" "ResponseError hx-on event name not correct"
}
]
] ]
/// Tests for the HxHeaders module /// Tests for the HxHeaders module
@@ -572,7 +608,7 @@ let attributes =
} }
test "_hxOnHxEvent succeeds" { test "_hxOnHxEvent succeeds" {
strong [ _hxOnHxEvent BeforeSwap "changeStuff()" ] [] strong [ _hxOnHxEvent BeforeSwap "changeStuff()" ] []
|> shouldRender """<strong hx-on::before:swap="changeStuff()"></strong>""" |> shouldRender """<strong hx-on:htmx:before:swap="changeStuff()"></strong>"""
} }
test "_hxPatch succeeds" { test "_hxPatch succeeds" {
div [ _hxPatch "/arrrgh" ] [] |> shouldRender """<div hx-patch="/arrrgh"></div>""" div [ _hxPatch "/arrrgh" ] [] |> shouldRender """<div hx-patch="/arrrgh"></div>"""
@@ -598,6 +634,18 @@ let attributes =
test "_hxSelectOob succeeds" { test "_hxSelectOob succeeds" {
section [ _hxSelectOob "#oob" ] [] |> shouldRender """<section hx-select-oob="#oob"></section>""" section [ _hxSelectOob "#oob" ] [] |> shouldRender """<section hx-select-oob="#oob"></section>"""
} }
testList "_hxStatus" [
test "succeeds for a full code with no action" {
div [ _hxStatus "403" HxSwap.After "" ] [] |> shouldRender """<div hx-status:403="swap:after"></div>"""
}
test "succeeds for a two-digit range with action" {
span [ _hxStatus "41" HxSwap.Before "target:main" ] []
|> shouldRender """<span hx-status:41x="swap:before target:main"></span>"""
}
test "succeeds for a one-digit range with no action" {
p [ _hxStatus "5" HxSwap.None "" ] [] |> shouldRender """<p hx-status:5xx="swap:none"></p>"""
}
]
test "_hxSwap succeeds" { test "_hxSwap succeeds" {
del [ _hxSwap "innerHTML" ] [] |> shouldRender """<del hx-swap="innerHTML"></del>""" del [ _hxSwap "innerHTML" ] [] |> shouldRender """<del hx-swap="innerHTML"></del>"""
} }
@@ -864,15 +912,7 @@ let hxEventObs =
} }
test "ToHxOnString succeeds" { test "ToHxOnString succeeds" {
Expect.equal Expect.equal
(AfterProcessNode.ToHxOnString()) "after:init" "AfterProcessNode hx-on event name not correct" (AfterProcessNode.ToHxOnString()) "after:process" "AfterProcessNode hx-on event name not correct"
}
]
testList "AfterSettle" [
test "ToString succeeds" {
Expect.equal (string AfterSettle) "afterSettle" "AfterSettle event name not correct"
}
test "ToHxOnString succeeds" {
Expect.equal (AfterSettle.ToHxOnString()) "after:swap" "AfterSettle hx-on event name not correct"
} }
] ]
testList "BeforeCleanupElement" [ testList "BeforeCleanupElement" [
@@ -912,7 +952,7 @@ let hxEventObs =
} }
test "ToHxOnString succeeds" { test "ToHxOnString succeeds" {
Expect.equal Expect.equal
(BeforeProcessNode.ToHxOnString()) "before:init" "BeforeProcessNode hx-on event name not correct" (BeforeProcessNode.ToHxOnString()) "before:process" "BeforeProcessNode hx-on event name not correct"
} }
] ]
testList "BeforeSend" [ testList "BeforeSend" [
@@ -1049,14 +1089,6 @@ let hxEventObs =
"PushedIntoHistory hx-on event name not correct" "PushedIntoHistory hx-on event name not correct"
} }
] ]
testList "ResponseError" [
test "ToString succeeds" {
Expect.equal (string ResponseError) "responseError" "ResponseError event name not correct"
}
test "ToHxOnString succeeds" {
Expect.equal (ResponseError.ToHxOnString()) "error" "ResponseError hx-on event name not correct"
}
]
testList "SendError" [ testList "SendError" [
test "ToString succeeds" { test "ToString succeeds" {
Expect.equal (string SendError) "sendError" "SendError event name not correct" Expect.equal (string SendError) "sendError" "SendError event name not correct"
@@ -1144,7 +1176,7 @@ let hxEventObs =
Expect.equal (string XhrAbort) "xhr:abort" "XhrAbort event name not correct" Expect.equal (string XhrAbort) "xhr:abort" "XhrAbort event name not correct"
} }
test "ToHxOnString succeeds" { test "ToHxOnString succeeds" {
Expect.equal (XhrAbort.ToHxOnString()) "xhr:abort" "XhrAbort hx-on event name not correct" Expect.equal (XhrAbort.ToHxOnString()) "error" "XhrAbort hx-on event name not correct"
} }
] ]
testList "XhrLoadEnd" [ testList "XhrLoadEnd" [
@@ -1152,7 +1184,7 @@ let hxEventObs =
Expect.equal (string XhrLoadEnd) "xhr:loadend" "XhrLoadEnd event name not correct" Expect.equal (string XhrLoadEnd) "xhr:loadend" "XhrLoadEnd event name not correct"
} }
test "ToHxOnString succeeds" { test "ToHxOnString succeeds" {
Expect.equal (XhrLoadEnd.ToHxOnString()) "xhr:loadend" "XhrLoadEnd hx-on event name not correct" Expect.equal (XhrLoadEnd.ToHxOnString()) "finally:request" "XhrLoadEnd hx-on event name not correct"
} }
] ]
testList "XhrLoadStart" [ testList "XhrLoadStart" [
+50 -14
View File
@@ -71,8 +71,11 @@ type HxEvent =
/// <summary>Triggered after an AJAX request has completed processing a successful response</summary> /// <summary>Triggered after an AJAX request has completed processing a successful response</summary>
| [<System.Obsolete "Removed in v4; use AfterInit">] AfterOnLoad | [<System.Obsolete "Removed in v4; use AfterInit">] AfterOnLoad
/// <summary>Triggered after htmx has initialized a DOM node or subtree</summary>
| AfterProcess
/// <summary>Triggered after htmx has initialized a node</summary> /// <summary>Triggered after htmx has initialized a node</summary>
| [<System.Obsolete "Removed in v4; use AfterInit">] AfterProcessNode | [<System.Obsolete "Removed in v4; use AfterProcess">] AfterProcessNode
/// <summary>Triggered after new content is saved to the history cache</summary> /// <summary>Triggered after new content is saved to the history cache</summary>
| AfterPushIntoHistory | AfterPushIntoHistory
@@ -84,7 +87,7 @@ type HxEvent =
| AfterRequest | AfterRequest
/// <summary>Triggered after the DOM has settled</summary> /// <summary>Triggered after the DOM has settled</summary>
| [<System.Obsolete "Removed in v4; use AfterSwap">] AfterSettle | AfterSettle
/// <summary>Triggered after a Server Sent Events (SSE) message is read</summary> /// <summary>Triggered after a Server Sent Events (SSE) message is read</summary>
| AfterSseMessage | AfterSseMessage
@@ -116,12 +119,18 @@ type HxEvent =
/// <summary>Triggered before any response processing occurs</summary> /// <summary>Triggered before any response processing occurs</summary>
| [<System.Obsolete "Removed in v4; use BeforeInit">] BeforeOnLoad | [<System.Obsolete "Removed in v4; use BeforeInit">] BeforeOnLoad
/// <summary>Triggered before htmx begins processing a DOM node or subtree</summary>
| BeforeProcess
/// <summary>Triggered before htmx initializes a node</summary> /// <summary>Triggered before htmx initializes a node</summary>
| [<System.Obsolete "Removed in v4; use BeforeInit">] BeforeProcessNode | [<System.Obsolete "Removed in v4; use BeforeProcess">] BeforeProcessNode
/// <summary>Triggered before an HTTP request is made</summary> /// <summary>Triggered before an HTTP request is made</summary>
| BeforeRequest | BeforeRequest
/// <summary>Triggered before an HTTP response is processed (cancelable)</summary>
| BeforeResponse
/// <summary>Triggered before a history restore request is made</summary> /// <summary>Triggered before a history restore request is made</summary>
| BeforeRestoreHistory | BeforeRestoreHistory
@@ -199,7 +208,7 @@ type HxEvent =
| [<System.Obsolete "Removed in v4; use AfterPushIntoHistory (or AfterHistoryUpdate to catch new and updated content)">] PushedIntoHistory | [<System.Obsolete "Removed in v4; use AfterPushIntoHistory (or AfterHistoryUpdate to catch new and updated content)">] PushedIntoHistory
/// <summary>Triggered when an HTTP response error (non-200 or 300 response code) occurs</summary> /// <summary>Triggered when an HTTP response error (non-200 or 300 response code) occurs</summary>
| [<System.Obsolete "Removed in v4; use Error (all errors consolidated)">] ResponseError | ResponseError
/// <summary>Triggered when a network error prevents an HTTP request from happening</summary> /// <summary>Triggered when a network error prevents an HTTP request from happening</summary>
| [<System.Obsolete "Removed in v4; use Error (all errors consolidated)">] SendError | [<System.Obsolete "Removed in v4; use Error (all errors consolidated)">] SendError
@@ -229,10 +238,10 @@ type HxEvent =
| [<System.Obsolete "Removed in v4; use native validation API">] ValidationHalted | [<System.Obsolete "Removed in v4; use native validation API">] ValidationHalted
/// <summary>Triggered when an ajax request aborts</summary> /// <summary>Triggered when an ajax request aborts</summary>
| [<System.Obsolete "Removed in v4; use fetch event listeners">] XhrAbort | [<System.Obsolete "Removed in v4; use fetch event listeners or Error event">] XhrAbort
/// <summary>Triggered when an ajax request ends</summary> /// <summary>Triggered when an ajax request ends</summary>
| [<System.Obsolete "Removed in v4; use fetch event listeners">] XhrLoadEnd | [<System.Obsolete "Removed in v4; use fetch event listeners or FinallyRequest event">] XhrLoadEnd
/// <summary>Triggered when an ajax request starts</summary> /// <summary>Triggered when an ajax request starts</summary>
| [<System.Obsolete "Removed in v4; use fetch event listeners">] XhrLoadStart | [<System.Obsolete "Removed in v4; use fetch event listeners">] XhrLoadStart
@@ -247,11 +256,11 @@ type HxEvent =
AfterHistoryUpdate, ("afterHistoryUpdate", "after:history:update") AfterHistoryUpdate, ("afterHistoryUpdate", "after:history:update")
AfterInit, ("afterInit", "after:init") AfterInit, ("afterInit", "after:init")
AfterOnLoad, ("afterOnLoad", "after:init") AfterOnLoad, ("afterOnLoad", "after:init")
AfterProcessNode, ("afterProcessNode", "after:init") AfterProcessNode, ("afterProcessNode", "after:process")
AfterPushIntoHistory, ("afterPushIntoHistory", "after:push:into:history") AfterPushIntoHistory, ("afterPushIntoHistory", "after:push:into:history")
AfterReplaceIntoHistory, ("afterReplaceIntoHistory", "after:replace:into:history") AfterReplaceIntoHistory, ("afterReplaceIntoHistory", "after:replace:into:history")
AfterRequest, ("afterRequest", "after:request") AfterRequest, ("afterRequest", "after:request")
AfterSettle, ("afterSettle", "after:swap") AfterSettle, ("afterSettle", "after:settle")
AfterSseMessage, ("afterSseMessage", "after:sse:message") AfterSseMessage, ("afterSseMessage", "after:sse:message")
AfterSseStream, ("afterSseStream", "after:sse:stream") AfterSseStream, ("afterSseStream", "after:sse:stream")
AfterSwap, ("afterSwap", "after:swap") AfterSwap, ("afterSwap", "after:swap")
@@ -262,9 +271,11 @@ type HxEvent =
BeforeHistoryUpdate, ("beforeHistoryUpdate", "before:history:update") BeforeHistoryUpdate, ("beforeHistoryUpdate", "before:history:update")
BeforeInit, ("beforeInit", "before:init") BeforeInit, ("beforeInit", "before:init")
BeforeOnLoad, ("beforeOnLoad", "before:init") BeforeOnLoad, ("beforeOnLoad", "before:init")
BeforeProcessNode, ("beforeProcessNode", "before:init") BeforeProcess, ("beforeProcess", "before:process")
BeforeRestoreHistory, ("beforeRestoreHistory", "before:restore:history") BeforeProcessNode, ("beforeProcessNode", "before:process")
BeforeRequest, ("beforeRequest", "before:request") BeforeRequest, ("beforeRequest", "before:request")
BeforeResponse, ("beforeResponse", "before:response")
BeforeRestoreHistory, ("beforeRestoreHistory", "before:restore:history")
BeforeSend, ("beforeSend", "before:request") BeforeSend, ("beforeSend", "before:request")
BeforeSseMessage, ("beforeSseMessage", "before:sse:message") BeforeSseMessage, ("beforeSseMessage", "before:sse:message")
BeforeSseReconnect, ("beforeSseReconnect", "before:sse:reconnect") BeforeSseReconnect, ("beforeSseReconnect", "before:sse:reconnect")
@@ -288,7 +299,7 @@ type HxEvent =
OobErrorNoTarget, ("oobErrorNoTarget", "error") OobErrorNoTarget, ("oobErrorNoTarget", "error")
Prompt, ("prompt", "prompt") Prompt, ("prompt", "prompt")
PushedIntoHistory, ("pushedIntoHistory", "after:push:into:history") PushedIntoHistory, ("pushedIntoHistory", "after:push:into:history")
ResponseError, ("responseError", "error") ResponseError, ("responseError", "response:error")
SendError, ("sendError", "error") SendError, ("sendError", "error")
SseError, ("sseError", "error") SseError, ("sseError", "error")
SseOpen, ("sseOpen", "sse-open") SseOpen, ("sseOpen", "sse-open")
@@ -298,8 +309,8 @@ type HxEvent =
ValidationValidate, ("validation:validate", "validation:validate") ValidationValidate, ("validation:validate", "validation:validate")
ValidationFailed, ("validation:failed", "validation:failed") ValidationFailed, ("validation:failed", "validation:failed")
ValidationHalted, ("validation:halted", "validation:halted") ValidationHalted, ("validation:halted", "validation:halted")
XhrAbort, ("xhr:abort", "xhr:abort") XhrAbort, ("xhr:abort", "error")
XhrLoadEnd, ("xhr:loadend", "xhr:loadend") XhrLoadEnd, ("xhr:loadend", "finally:request")
XhrLoadStart, ("xhr:loadstart", "xhr:loadstart") XhrLoadStart, ("xhr:loadstart", "xhr:loadstart")
XhrProgress, ("xhr:progress", "xhr:progress") XhrProgress, ("xhr:progress", "xhr:progress")
] ]
@@ -757,7 +768,7 @@ module HtmxAttrs =
/// <seealso cref="HxEvent" /> /// <seealso cref="HxEvent" />
/// <seealso href="https://htmx.org/attributes/hx-on/">Documentation</seealso> /// <seealso href="https://htmx.org/attributes/hx-on/">Documentation</seealso>
let _hxOnHxEvent (hxEvent: HxEvent) handler = let _hxOnHxEvent (hxEvent: HxEvent) handler =
_hxOnEvent $":{hxEvent.ToHxOnString()}" handler _hxOnEvent $"htmx:{hxEvent.ToHxOnString()}" handler
/// <summary>Filters the parameters that will be submitted with a request</summary> /// <summary>Filters the parameters that will be submitted with a request</summary>
/// <param name="toInclude">The fields to include (use <c>HxParams</c> to generate this value)</param> /// <param name="toInclude">The fields to include (use <c>HxParams</c> to generate this value)</param>
@@ -852,6 +863,31 @@ module HtmxAttrs =
let _hxSelectOob selectors = let _hxSelectOob selectors =
attr "hx-select-oob" selectors attr "hx-select-oob" selectors
/// <summary>Define a response to an HTTP status code or code range</summary>
/// <param name="code">
/// The code or code range; for a code, pass the full code as a string (ex. <c>404</c>), and for a range, pass a
/// string with one or two characters (ex. <c>5</c> for all 500s, <c>42</c> for 420-429)
/// </param>
/// <param name="swap">The swap style to use for this action(use <c>HxSwap</c> values)</param>
/// <param name="action">
/// The action which should be taken for responses matching the status code; can be a combination of the following,
/// with each item separated by a space:
/// <ul>
/// <li><c>target:</c> and a CSS selector for the swap target (overrides original <c>hx-target</c>)</li>
/// <li><c>select:</c> and a CSS selector for the content to be swapped (overrides original <c>hx-select</c>)</li>
/// <li><c>push:</c> and <c>true</c>, <c>false</c>, or a URL; <c>false</c> does not update URL history, <c>true</c>
/// updates with the current URL, and a string URL will be pushed into history as-is</li>
/// <li><c>replace:</c> and <c>true</c>, <c>false</c>, or a URL; same as <c>push:</c>, but replaces in history
/// instead</li>
/// <li><c>transition:</c> and <c>true</c> or <c>false</c>; whether to use view transitions</li>
/// </ul>
/// If only the type of swap is being changed, pass an empty string to this parameter.
/// </param>
/// <seealso href="https://four.htmx.org/reference/attributes/hx-status">Documentation</seealso>
let _hxStatus code swap action =
let range = $"%s{code}xx".Substring(0, 3)
attr $"hx-status:{range}" ($"swap:%s{swap} %s{action}".Trim())
/// <summary> /// <summary>
/// Controls how the response content is swapped into the DOM (e.g. <c>outerHTML</c> or <c>beforeEnd</c>) /// Controls how the response content is swapped into the DOM (e.g. <c>outerHTML</c> or <c>beforeEnd</c>)
/// </summary> /// </summary>