Work on 1.8.0 (#5)

- Bump library version
- Bump htmx script version
- Add hx-replace-url / HX-Replace-Url
- Add hx-select-oob
- Change hx-push-url to accept a string
- Add HX-Push-Url, obsolete HX-Push
This commit is contained in:
Daniel J. Summers 2022-07-13 20:42:55 -04:00
parent c587a28770
commit 3ae3a3b26c
9 changed files with 1072 additions and 948 deletions

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.7.0</VersionPrefix> <TargetFramework>net6.0</TargetFramework>
<PackageReleaseNotes>Support new attributes/headers in htmx 1.7.0</PackageReleaseNotes> <VersionPrefix>1.8.0</VersionPrefix>
<PackageReleaseNotes>Support new attributes/headers in htmx 1.8.0</PackageReleaseNotes>
<Authors>danieljsummers</Authors> <Authors>danieljsummers</Authors>
<Company>Bit Badger Solutions</Company> <Company>Bit Badger Solutions</Company>
<PackageProjectUrl>https://github.com/bit-badger/Giraffe.Htmx</PackageProjectUrl> <PackageProjectUrl>https://github.com/bit-badger/Giraffe.Htmx</PackageProjectUrl>

View File

@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile> <GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup> </PropertyGroup>

View File

@ -207,25 +207,25 @@ module HandlerTests =
let next (ctx : HttpContext) = Task.FromResult (Some ctx) let next (ctx : HttpContext) = Task.FromResult (Some ctx)
[<Fact>] [<Fact>]
let ``withHxPush succeeds`` () = let ``withHxPushUrl succeeds`` () =
let ctx = Substitute.For<HttpContext> () let ctx = Substitute.For<HttpContext> ()
let dic = HeaderDictionary () let dic = HeaderDictionary ()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
task { task {
let! _ = withHxPush "/a-new-url" next ctx let! _ = withHxPushUrl "/a-new-url" next ctx
Assert.True (dic.ContainsKey "HX-Push") Assert.True (dic.ContainsKey "HX-Push-Url")
Assert.Equal ("/a-new-url", dic.["HX-Push"].[0]) Assert.Equal ("/a-new-url", dic["HX-Push-Url"][0])
} }
[<Fact>] [<Fact>]
let ``withHxNoPush succeeds`` () = let ``withHxNoPushUrl succeeds`` () =
let ctx = Substitute.For<HttpContext> () let ctx = Substitute.For<HttpContext> ()
let dic = HeaderDictionary () let dic = HeaderDictionary ()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
task { task {
let! _ = withHxNoPush next ctx let! _ = withHxNoPushUrl next ctx
Assert.True (dic.ContainsKey "HX-Push") Assert.True (dic.ContainsKey "HX-Push-Url")
Assert.Equal ("false", dic.["HX-Push"].[0]) Assert.Equal ("false", dic["HX-Push-Url"][0])
} }
[<Fact>] [<Fact>]
@ -236,7 +236,7 @@ module HandlerTests =
task { task {
let! _ = withHxRedirect "/somewhere-else" next ctx let! _ = withHxRedirect "/somewhere-else" next ctx
Assert.True (dic.ContainsKey "HX-Redirect") Assert.True (dic.ContainsKey "HX-Redirect")
Assert.Equal ("/somewhere-else", dic.["HX-Redirect"].[0]) Assert.Equal ("/somewhere-else", dic["HX-Redirect"][0])
} }
[<Fact>] [<Fact>]
@ -247,7 +247,7 @@ module HandlerTests =
task { task {
let! _ = withHxRefresh true next ctx let! _ = withHxRefresh true next ctx
Assert.True (dic.ContainsKey "HX-Refresh") Assert.True (dic.ContainsKey "HX-Refresh")
Assert.Equal ("true", dic.["HX-Refresh"].[0]) Assert.Equal ("true", dic["HX-Refresh"][0])
} }
[<Fact>] [<Fact>]
@ -258,7 +258,29 @@ module HandlerTests =
task { task {
let! _ = withHxRefresh false next ctx let! _ = withHxRefresh false next ctx
Assert.True (dic.ContainsKey "HX-Refresh") Assert.True (dic.ContainsKey "HX-Refresh")
Assert.Equal ("false", dic.["HX-Refresh"].[0]) Assert.Equal ("false", dic["HX-Refresh"][0])
}
[<Fact>]
let ``withHxReplaceUrl succeeds`` () =
let ctx = Substitute.For<HttpContext> ()
let dic = HeaderDictionary ()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
task {
let! _ = withHxReplaceUrl "/a-substitute-url" next ctx
Assert.True (dic.ContainsKey "HX-Replace-Url")
Assert.Equal ("/a-substitute-url", dic["HX-Replace-Url"][0])
}
[<Fact>]
let ``withHxNoReplaceUrl succeeds`` () =
let ctx = Substitute.For<HttpContext> ()
let dic = HeaderDictionary ()
ctx.Response.Headers.ReturnsForAnyArgs dic |> ignore
task {
let! _ = withHxNoReplaceUrl next ctx
Assert.True (dic.ContainsKey "HX-Replace-Url")
Assert.Equal ("false", dic["HX-Replace-Url"][0])
} }
[<Fact>] [<Fact>]
@ -269,7 +291,7 @@ module HandlerTests =
task { task {
let! _ = withHxRetarget "#somewhereElse" next ctx let! _ = withHxRetarget "#somewhereElse" next ctx
Assert.True (dic.ContainsKey "HX-Retarget") Assert.True (dic.ContainsKey "HX-Retarget")
Assert.Equal ("#somewhereElse", dic.["HX-Retarget"].[0]) Assert.Equal ("#somewhereElse", dic["HX-Retarget"][0])
} }
[<Fact>] [<Fact>]
@ -280,7 +302,7 @@ module HandlerTests =
task { task {
let! _ = withHxTrigger "doSomething" next ctx let! _ = withHxTrigger "doSomething" next ctx
Assert.True (dic.ContainsKey "HX-Trigger") Assert.True (dic.ContainsKey "HX-Trigger")
Assert.Equal ("doSomething", dic.["HX-Trigger"].[0]) Assert.Equal ("doSomething", dic["HX-Trigger"][0])
} }
[<Fact>] [<Fact>]
@ -291,7 +313,7 @@ module HandlerTests =
task { task {
let! _ = withHxTriggerMany [ "blah", "foo"; "bleh", "bar" ] next ctx let! _ = withHxTriggerMany [ "blah", "foo"; "bleh", "bar" ] next ctx
Assert.True (dic.ContainsKey "HX-Trigger") Assert.True (dic.ContainsKey "HX-Trigger")
Assert.Equal ("""{ "blah": "foo", "bleh": "bar" }""", dic.["HX-Trigger"].[0]) Assert.Equal ("""{ "blah": "foo", "bleh": "bar" }""", dic["HX-Trigger"][0])
} }
[<Fact>] [<Fact>]
@ -302,7 +324,7 @@ module HandlerTests =
task { task {
let! _ = withHxTriggerAfterSettle "byTheWay" next ctx let! _ = withHxTriggerAfterSettle "byTheWay" next ctx
Assert.True (dic.ContainsKey "HX-Trigger-After-Settle") Assert.True (dic.ContainsKey "HX-Trigger-After-Settle")
Assert.Equal ("byTheWay", dic.["HX-Trigger-After-Settle"].[0]) Assert.Equal ("byTheWay", dic["HX-Trigger-After-Settle"][0])
} }
[<Fact>] [<Fact>]
@ -313,7 +335,7 @@ module HandlerTests =
task { task {
let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx let! _ = withHxTriggerManyAfterSettle [ "oof", "ouch"; "hmm", "uh" ] next ctx
Assert.True (dic.ContainsKey "HX-Trigger-After-Settle") Assert.True (dic.ContainsKey "HX-Trigger-After-Settle")
Assert.Equal ("""{ "oof": "ouch", "hmm": "uh" }""", dic.["HX-Trigger-After-Settle"].[0]) Assert.Equal ("""{ "oof": "ouch", "hmm": "uh" }""", dic["HX-Trigger-After-Settle"][0])
} }
[<Fact>] [<Fact>]
@ -324,7 +346,7 @@ module HandlerTests =
task { task {
let! _ = withHxTriggerAfterSwap "justASec" next ctx let! _ = withHxTriggerAfterSwap "justASec" next ctx
Assert.True (dic.ContainsKey "HX-Trigger-After-Swap") Assert.True (dic.ContainsKey "HX-Trigger-After-Swap")
Assert.Equal ("justASec", dic.["HX-Trigger-After-Swap"].[0]) Assert.Equal ("justASec", dic["HX-Trigger-After-Swap"][0])
} }
[<Fact>] [<Fact>]
@ -335,6 +357,5 @@ module HandlerTests =
task { task {
let! _ = withHxTriggerManyAfterSwap [ "this", "1"; "that", "2" ] next ctx let! _ = withHxTriggerManyAfterSwap [ "this", "1"; "that", "2" ] next ctx
Assert.True (dic.ContainsKey "HX-Trigger-After-Swap") Assert.True (dic.ContainsKey "HX-Trigger-After-Swap")
Assert.Equal ("""{ "this": "1", "that": "2" }""", dic.["HX-Trigger-After-Swap"].[0]) Assert.Equal ("""{ "this": "1", "that": "2" }""", dic["HX-Trigger-After-Swap"][0])
} }

View File

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<Description>htmx header extensions and helpers for Giraffe</Description> <Description>htmx header extensions and helpers for Giraffe</Description>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>

View File

@ -6,7 +6,7 @@ open System
/// 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]
/// Extensions to the header dictionary /// Extensions to the header dictionary
type IHeaderDictionary with type IHeaderDictionary with
@ -62,13 +62,21 @@ module Handlers =
|> String.concat ", " |> String.concat ", "
|> sprintf "{ %s }" |> sprintf "{ %s }"
// Pushes a new url into the history stack /// Pushes a new url into the history stack
let withHxPush : string -> HttpHandler = let withHxPushUrl : string -> HttpHandler =
setHttpHeader "HX-Push" setHttpHeader "HX-Push-Url"
// Explicitly do not push a new URL into the history stack /// Explicitly do not push a new URL into the history stack
let withHxNoPush : HttpHandler = let withHxNoPushUrl : HttpHandler =
toLowerBool false |> withHxPush toLowerBool false |> withHxPushUrl
/// Pushes a new url into the history stack
[<Obsolete "Use withHxPushUrl; HX-Push was replaced by HX-Push-Url in v1.8.0">]
let withHxPush = withHxPushUrl
/// Explicitly do not push a new URL into the history stack
[<Obsolete "Use withHxNoPushUrl; HX-Push was replaced by HX-Push-Url in v1.8.0">]
let withHxNoPush = withHxNoPushUrl
/// Can be used to do a client-side redirect to a new location /// Can be used to do a client-side redirect to a new location
let withHxRedirect : string -> HttpHandler = let withHxRedirect : string -> HttpHandler =
@ -78,6 +86,14 @@ module Handlers =
let withHxRefresh : bool -> HttpHandler = let withHxRefresh : bool -> HttpHandler =
toLowerBool >> setHttpHeader "HX-Refresh" toLowerBool >> setHttpHeader "HX-Refresh"
/// Replaces the current URL in the history stack
let withHxReplaceUrl : string -> HttpHandler =
setHttpHeader "HX-Replace-Url"
/// Explicitly do not replace the current URL in the history stack
let withHxNoReplaceUrl : HttpHandler =
toLowerBool false |> withHxReplaceUrl
/// Allows you to override the `hx-target` attribute /// Allows you to override the `hx-target` attribute
let withHxRetarget : string -> HttpHandler = let withHxRetarget : string -> HttpHandler =
setHttpHeader "HX-Retarget" setHttpHeader "HX-Retarget"

View File

@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile> <GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup> </PropertyGroup>

View File

@ -403,12 +403,16 @@ module Attributes =
[<Fact>] [<Fact>]
let ``_hxPushUrl succeeds`` () = let ``_hxPushUrl succeeds`` () =
dl [ _hxPushUrl ] [] |> shouldRender """<dl hx-push-url></dl>""" dl [ _hxPushUrl "/a-b-c" ] [] |> shouldRender """<dl hx-push-url="/a-b-c"></dl>"""
[<Fact>] [<Fact>]
let ``_hxPut succeeds`` () = let ``_hxPut succeeds`` () =
s [ _hxPut "/take-this" ] [] |> shouldRender """<s hx-put="/take-this"></s>""" s [ _hxPut "/take-this" ] [] |> shouldRender """<s hx-put="/take-this"></s>"""
[<Fact>]
let ``_hxReplaceUrl succeeds`` () =
p [ _hxReplaceUrl "/something-else" ] [] |> shouldRender """<p hx-replace-url="/something-else"></p>"""
[<Fact>] [<Fact>]
let ``_hxRequest succeeds`` () = let ``_hxRequest succeeds`` () =
u [ _hxRequest "noHeaders" ] [] |> shouldRender """<u hx-request="noHeaders"></u>""" u [ _hxRequest "noHeaders" ] [] |> shouldRender """<u hx-request="noHeaders"></u>"""
@ -417,6 +421,10 @@ module Attributes =
let ``_hxSelect succeeds`` () = let ``_hxSelect succeeds`` () =
nav [ _hxSelect "#navbar" ] [] |> shouldRender """<nav hx-select="#navbar"></nav>""" nav [ _hxSelect "#navbar" ] [] |> shouldRender """<nav hx-select="#navbar"></nav>"""
[<Fact>]
let ``_hxSelectOob succeeds`` () =
section [ _hxSelectOob "#oob" ] [] |> shouldRender """<section hx-select-oob="#oob"></section>"""
[<Fact>] [<Fact>]
let ``_hxSse succeeds`` () = let ``_hxSse succeeds`` () =
footer [ _hxSse "connect:/my-events" ] [] |> shouldRender """<footer hx-sse="connect:/my-events"></footer>""" footer [ _hxSse "connect:/my-events" ] [] |> shouldRender """<footer hx-sse="connect:/my-events"></footer>"""
@ -457,11 +465,13 @@ module Script =
[<Fact>] [<Fact>]
let ``Script.minified succeeds`` () = let ``Script.minified succeeds`` () =
let html = RenderView.AsString.htmlNode Script.minified let html = RenderView.AsString.htmlNode Script.minified
Assert.Equal ("""<script src="https://unpkg.com/htmx.org@1.7.0" integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous"></script>""", Assert.Equal
("""<script src="https://unpkg.com/htmx.org@1.8.0" integrity="sha384-cZuAZ+ZbwkNRnrKi05G/fjBX+azI9DNOkNYysZ0I/X5ZFgsmMiBXgDZof30F5ofc" crossorigin="anonymous"></script>""",
html) html)
[<Fact>] [<Fact>]
let ``Script.unminified succeeds`` () = let ``Script.unminified succeeds`` () =
let html = RenderView.AsString.htmlNode Script.unminified let html = RenderView.AsString.htmlNode Script.unminified
Assert.Equal ("""<script src="https://unpkg.com/htmx.org@1.7.0/dist/htmx.js" integrity="sha384-ESk4PjE7dwjGkEciohREmmf8rLMX0E95MKwxM3bvC90sZ3XbF2TELnVk2w7bX0d9" crossorigin="anonymous"></script>""", Assert.Equal
("""<script src="https://unpkg.com/htmx.org@1.8.0/dist/htmx.js" integrity="sha384-mrsv860ohrJ5KkqRxwXXj6OIT6sONUxOd+1kvbqW351hQd7JlfFnM0tLetA76GU0" crossorigin="anonymous"></script>""",
html) html)

View File

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<Description>Extensions to Giraffe View Engine to support htmx attributes and their values</Description> <Description>Extensions to Giraffe View Engine to support htmx attributes and their values</Description>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>

View File

@ -11,8 +11,10 @@ let private toJson (kvps : (string * string) list) =
/// Valid values for the `hx-encoding` attribute /// Valid values for the `hx-encoding` attribute
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxEncoding = module HxEncoding =
/// A standard HTTP form /// A standard HTTP form
let Form = "application/x-www-form-urlencoded" let Form = "application/x-www-form-urlencoded"
/// A multipart form (used for file uploads) /// A multipart form (used for file uploads)
let MultipartForm = "multipart/form-data" let MultipartForm = "multipart/form-data"
@ -20,6 +22,7 @@ module HxEncoding =
/// Helper to create the `hx-headers` attribute /// Helper to create the `hx-headers` attribute
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxHeaders = module HxHeaders =
/// Create headers from a list of key/value pairs /// Create headers from a list of key/value pairs
let From = toJson let From = toJson
@ -27,12 +30,16 @@ module HxHeaders =
/// Values / helpers for the `hx-params` attribute /// Values / helpers for the `hx-params` attribute
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxParams = module HxParams =
/// Include all parameters /// Include all parameters
let All = "*" let All = "*"
/// Include no parameters /// Include no parameters
let None = "none" let None = "none"
/// Include the specified parameters /// Include the specified parameters
let With fields = match fields with [] -> "" | _ -> fields |> List.reduce (fun acc it -> $"{acc},{it}") let With fields = match fields with [] -> "" | _ -> fields |> List.reduce (fun acc it -> $"{acc},{it}")
/// Exclude the specified parameters /// Exclude the specified parameters
let Except fields = With fields |> sprintf "not %s" let Except fields = With fields |> sprintf "not %s"
@ -40,18 +47,23 @@ module HxParams =
/// Helpers to define `hx-request` attribute values /// Helpers to define `hx-request` attribute values
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxRequest = module HxRequest =
/// Convert a boolean to its lowercase string equivalent /// Convert a boolean to its lowercase string equivalent
let private toLowerBool (it : bool) = let private toLowerBool (it : bool) =
(string it).ToLowerInvariant () (string it).ToLowerInvariant ()
/// Configure the request with various options /// Configure the request with various options
let Configure (opts : string list) = let Configure (opts : string list) =
opts opts
|> String.concat ", " |> String.concat ", "
|> sprintf "{ %s }" |> sprintf "{ %s }"
/// Set a timeout (in milliseconds) /// Set a timeout (in milliseconds)
let Timeout (ms : int) = $"\"timeout\": {ms}" let Timeout (ms : int) = $"\"timeout\": {ms}"
/// Include or exclude credentials from the request /// Include or exclude credentials from the request
let Credentials = toLowerBool >> sprintf "\"credentials\": %s" let Credentials = toLowerBool >> sprintf "\"credentials\": %s"
/// Exclude or include headers from the request /// Exclude or include headers from the request
let NoHeaders = toLowerBool >> sprintf "\"noHeaders\": %s" let NoHeaders = toLowerBool >> sprintf "\"noHeaders\": %s"
@ -59,18 +71,25 @@ module HxRequest =
/// Valid values for the `hx-swap` attribute (may be combined with swap/settle/scroll/show config) /// Valid values for the `hx-swap` attribute (may be combined with swap/settle/scroll/show config)
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxSwap = module HxSwap =
/// The default, replace the inner html of the target element /// The default, replace the inner html of the target element
let InnerHtml = "innerHTML" let InnerHtml = "innerHTML"
/// Replace the entire target element with the response /// Replace the entire target element with the response
let OuterHtml = "outerHTML" let OuterHtml = "outerHTML"
/// Insert the response before the target element /// Insert the response before the target element
let BeforeBegin = "beforebegin" let BeforeBegin = "beforebegin"
/// Insert the response before the first child of the target element /// Insert the response before the first child of the target element
let AfterBegin = "afterbegin" let AfterBegin = "afterbegin"
/// Insert the response after the last child of the target element /// Insert the response after the last child of the target element
let BeforeEnd = "beforeend" let BeforeEnd = "beforeend"
/// Insert the response after the target element /// Insert the response after the target element
let AfterEnd = "afterend" let AfterEnd = "afterend"
/// Does not append content from response (out of band items will still be processed). /// Does not append content from response (out of band items will still be processed).
let None = "none" let None = "none"
@ -78,70 +97,100 @@ module HxSwap =
/// Helpers for the `hx-trigger` attribute /// Helpers for the `hx-trigger` attribute
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxTrigger = module HxTrigger =
/// Append a filter to a trigger /// Append a filter to a trigger
let private appendFilter filter (trigger : string) = let private appendFilter filter (trigger : string) =
match trigger.Contains "[" with match trigger.Contains "[" with
| true -> | true ->
let parts = trigger.Split ('[', ']') let parts = trigger.Split ('[', ']')
$"{parts.[0]}[{parts.[1]}&&{filter}]" $"{parts[0]}[{parts[1]}&&{filter}]"
| false -> $"{trigger}[{filter}]" | false -> $"{trigger}[{filter}]"
/// Trigger the event on a click /// Trigger the event on a click
let Click = "click" let Click = "click"
/// Trigger the event on page load /// Trigger the event on page load
let Load = "load" let Load = "load"
/// Trigger the event when the item is visible /// Trigger the event when the item is visible
let Revealed = "revealed" let Revealed = "revealed"
/// Trigger this event every [timing declaration] /// Trigger this event every [timing declaration]
let Every (duration : string) = $"every {duration}" let Every (duration : string) = $"every {duration}"
/// Helpers for defining filters /// Helpers for defining filters
module Filter = module Filter =
/// Only trigger the event if the `ALT` key is pressed /// Only trigger the event if the `ALT` key is pressed
let Alt = appendFilter "altKey" let Alt = appendFilter "altKey"
/// Only trigger the event if the `CTRL` key is pressed /// Only trigger the event if the `CTRL` key is pressed
let Ctrl = appendFilter "ctrlKey" let Ctrl = appendFilter "ctrlKey"
/// Only trigger the event if the `SHIFT` key is pressed /// Only trigger the event if the `SHIFT` key is pressed
let Shift = appendFilter "shiftKey" let Shift = appendFilter "shiftKey"
/// Only trigger the event if `CTRL+ALT` are pressed /// Only trigger the event if `CTRL+ALT` are pressed
let CtrlAlt = Ctrl >> Alt let CtrlAlt = Ctrl >> Alt
/// Only trigger the event if `CTRL+SHIFT` are pressed /// Only trigger the event if `CTRL+SHIFT` are pressed
let CtrlShift = Ctrl >> Shift let CtrlShift = Ctrl >> Shift
/// Only trigger the event if `CTRL+ALT+SHIFT` are pressed /// Only trigger the event if `CTRL+ALT+SHIFT` are pressed
let CtrlAltShift = CtrlAlt >> Shift let CtrlAltShift = CtrlAlt >> Shift
/// Only trigger the event if `ALT+SHIFT` are pressed /// Only trigger the event if `ALT+SHIFT` are pressed
let AltShift = Alt >> Shift let AltShift = Alt >> Shift
/// Append a modifier to the current trigger /// Append a modifier to the current trigger
let private appendModifier modifier current = let private appendModifier modifier current =
match current with "" -> modifier | _ -> $"{current} {modifier}" if current = "" then modifier else $"{current} {modifier}"
/// Only trigger once /// Only trigger once
let Once = appendModifier "once" let Once = appendModifier "once"
/// Trigger when changed /// Trigger when changed
let Changed = appendModifier "changed" let Changed = appendModifier "changed"
/// Delay execution; resets every time the event is seen /// Delay execution; resets every time the event is seen
let Delay = sprintf "delay:%s" >> appendModifier let Delay = sprintf "delay:%s" >> appendModifier
/// Throttle execution; ignore other events, fire when duration passes /// Throttle execution; ignore other events, fire when duration passes
let Throttle = sprintf "throttle:%s" >> appendModifier let Throttle = sprintf "throttle:%s" >> appendModifier
/// Trigger this event from a CSS selector /// Trigger this event from a CSS selector
let From = sprintf "from:%s" >> appendModifier let From = sprintf "from:%s" >> appendModifier
/// Trigger this event from the `document` object /// Trigger this event from the `document` object
let FromDocument = From "document" let FromDocument = From "document"
/// Trigger this event from the `window` object /// Trigger this event from the `window` object
let FromWindow = From "window" let FromWindow = From "window"
/// Trigger this event from the closest parent CSS selector /// Trigger this event from the closest parent CSS selector
let FromClosest = sprintf "closest %s" >> From let FromClosest = sprintf "closest %s" >> From
/// Trigger this event from the closest child CSS selector /// Trigger this event from the closest child CSS selector
let FromFind = sprintf "find %s" >> From let FromFind = sprintf "find %s" >> From
/// Target the given CSS selector with the results of this event /// Target the given CSS selector with the results of this event
let Target = sprintf "target:%s" >> appendModifier let Target = sprintf "target:%s" >> appendModifier
/// Prevent any further events from occurring after this one fires /// Prevent any further events from occurring after this one fires
let Consume = appendModifier "consume" let Consume = appendModifier "consume"
/// Configure queueing when events fire when others are in flight; if unspecified, the default is "last" /// Configure queueing when events fire when others are in flight; if unspecified, the default is "last"
let Queue = sprintf "queue:%s" >> appendModifier let Queue = sprintf "queue:%s" >> appendModifier
/// Queue the first event, discard all others (i.e., a FIFO queue of 1) /// Queue the first event, discard all others (i.e., a FIFO queue of 1)
let QueueFirst = Queue "first" let QueueFirst = Queue "first"
/// Queue the last event; discards current when another is received (i.e., a LIFO queue of 1) /// Queue the last event; discards current when another is received (i.e., a LIFO queue of 1)
let QueueLast = Queue "last" let QueueLast = Queue "last"
/// Queue all events; discard none /// Queue all events; discard none
let QueueAll = Queue "all" let QueueAll = Queue "all"
/// Queue no events; discard all /// Queue no events; discard all
let QueueNone = Queue "none" let QueueNone = Queue "none"
@ -149,6 +198,7 @@ module HxTrigger =
/// Helper to create the `hx-vals` attribute /// Helper to create the `hx-vals` attribute
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module HxVals = module HxVals =
/// Create values from a list of key/value pairs /// Create values from a list of key/value pairs
let From = toJson let From = toJson
@ -156,64 +206,100 @@ module HxVals =
/// Attributes and flags for htmx /// Attributes and flags for htmx
[<AutoOpen>] [<AutoOpen>]
module HtmxAttrs = module HtmxAttrs =
/// Progressively enhances anchors and forms to use AJAX requests (use `_hxNoBoost` to set to false) /// Progressively enhances anchors and forms to use AJAX requests (use `_hxNoBoost` to set to false)
let _hxBoost = attr "hx-boost" "true" let _hxBoost = attr "hx-boost" "true"
/// Shows a confirm() dialog before issuing a request /// Shows a confirm() dialog before issuing a request
let _hxConfirm = attr "hx-confirm" let _hxConfirm = attr "hx-confirm"
/// Issues a DELETE to the specified URL /// Issues a DELETE to the specified URL
let _hxDelete = attr "hx-delete" let _hxDelete = attr "hx-delete"
/// Disables htmx processing for the given node and any children nodes /// Disables htmx processing for the given node and any children nodes
let _hxDisable = flag "hx-disable" let _hxDisable = flag "hx-disable"
/// Disinherit all ("*") or specific htmx attributes /// Disinherit all ("*") or specific htmx attributes
let _hxDisinherit = attr "hx-disinherit" let _hxDisinherit = attr "hx-disinherit"
/// Changes the request encoding type /// Changes the request encoding type
let _hxEncoding = attr "hx-encoding" let _hxEncoding = attr "hx-encoding"
/// Extensions to use for this element /// Extensions to use for this element
let _hxExt = attr "hx-ext" let _hxExt = attr "hx-ext"
/// Issues a GET to the specified URL /// Issues a GET to the specified URL
let _hxGet = attr "hx-get" let _hxGet = attr "hx-get"
/// Adds to the headers that will be submitted with the request /// Adds to the headers that will be submitted with the request
let _hxHeaders = attr "hx-headers" let _hxHeaders = attr "hx-headers"
/// The element to snapshot and restore during history navigation /// The element to snapshot and restore during history navigation
let _hxHistoryElt = flag "hx-history-elt" let _hxHistoryElt = flag "hx-history-elt"
/// Includes additional data in AJAX requests /// Includes additional data in AJAX requests
let _hxInclude = attr "hx-include" let _hxInclude = attr "hx-include"
/// The element to put the htmx-request class on during the AJAX request /// The element to put the htmx-request class on during the AJAX request
let _hxIndicator = attr "hx-indicator" let _hxIndicator = attr "hx-indicator"
/// Overrides a previous `hx-boost` /// Overrides a previous `hx-boost`
let _hxNoBoost = attr "hx-boost" "false" let _hxNoBoost = attr "hx-boost" "false"
/// Filters the parameters that will be submitted with a request /// Filters the parameters that will be submitted with a request
let _hxParams = attr "hx-params" let _hxParams = attr "hx-params"
/// Issues a PATCH to the specified URL /// Issues a PATCH to the specified URL
let _hxPatch = attr "hx-patch" let _hxPatch = attr "hx-patch"
/// Issues a POST to the specified URL /// Issues a POST to the specified URL
let _hxPost = attr "hx-post" let _hxPost = attr "hx-post"
/// Preserves an element between requests /// Preserves an element between requests
let _hxPreserve = attr "hx-preserve" "true" let _hxPreserve = attr "hx-preserve" "true"
/// Shows a prompt before submitting a request /// Shows a prompt before submitting a request
let _hxPrompt = attr "hx-prompt" let _hxPrompt = attr "hx-prompt"
/// Pushes the URL into the location bar, creating a new history entry /// Pushes the URL into the location bar, creating a new history entry
let _hxPushUrl = flag "hx-push-url" let _hxPushUrl = attr "hx-push-url"
/// Issues a PUT to the specified URL /// Issues a PUT to the specified URL
let _hxPut = attr "hx-put" let _hxPut = attr "hx-put"
/// Replaces the current URL in the browser's history stack
let _hxReplaceUrl = attr "hx-replace-url"
/// Configures various aspects of the request /// Configures various aspects of the request
let _hxRequest = attr "hx-request" let _hxRequest = attr "hx-request"
/// Selects a subset of the server response to process /// Selects a subset of the server response to process
let _hxSelect = attr "hx-select" let _hxSelect = attr "hx-select"
/// Selects a subset of an out-of-band server response
let _hxSelectOob = attr "hx-select-oob"
/// Establishes and listens to Server Sent Event (SSE) sources for events /// Establishes and listens to Server Sent Event (SSE) sources for events
let _hxSse = attr "hx-sse" let _hxSse = attr "hx-sse"
/// Controls how the response content is swapped into the DOM (e.g. 'outerHTML' or 'beforeEnd') /// Controls how the response content is swapped into the DOM (e.g. 'outerHTML' or 'beforeEnd')
let _hxSwap = attr "hx-swap" let _hxSwap = attr "hx-swap"
/// Marks content in a response as being "Out of Band", i.e. swapped somewhere other than the target /// Marks content in a response as being "Out of Band", i.e. swapped somewhere other than the target
let _hxSwapOob = attr "hx-swap-oob" let _hxSwapOob = attr "hx-swap-oob"
/// Synchronize events based on another element /// Synchronize events based on another element
let _hxSync = attr "hx-sync" let _hxSync = attr "hx-sync"
/// Specifies the target element to be swapped /// Specifies the target element to be swapped
let _hxTarget = attr "hx-target" let _hxTarget = attr "hx-target"
/// Specifies the event that triggers the request /// Specifies the event that triggers the request
let _hxTrigger = attr "hx-trigger" let _hxTrigger = attr "hx-trigger"
/// Adds to the parameters that will be submitted with the request /// Adds to the parameters that will be submitted with the request
let _hxVals = attr "hx-vals" let _hxVals = attr "hx-vals"
/// Establishes a WebSocket or sends information to one /// Establishes a WebSocket or sends information to one
let _hxWs = attr "hx-ws" let _hxWs = attr "hx-ws"
@ -223,16 +309,12 @@ module Script =
/// Script tag to load the minified version from unpkg.com /// Script tag to load the minified version from unpkg.com
let minified = let minified =
script [ script [ _src "https://unpkg.com/htmx.org@1.8.0"
_src "https://unpkg.com/htmx.org@1.7.0" _integrity "sha384-cZuAZ+ZbwkNRnrKi05G/fjBX+azI9DNOkNYysZ0I/X5ZFgsmMiBXgDZof30F5ofc"
_integrity "sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" _crossorigin "anonymous" ] []
_crossorigin "anonymous"
] []
/// Script tag to load the unminified version from unpkg.com /// Script tag to load the unminified version from unpkg.com
let unminified = let unminified =
script [ script [ _src "https://unpkg.com/htmx.org@1.8.0/dist/htmx.js"
_src "https://unpkg.com/htmx.org@1.7.0/dist/htmx.js" _integrity "sha384-mrsv860ohrJ5KkqRxwXXj6OIT6sONUxOd+1kvbqW351hQd7JlfFnM0tLetA76GU0"
_integrity "sha384-ESk4PjE7dwjGkEciohREmmf8rLMX0E95MKwxM3bvC90sZ3XbF2TELnVk2w7bX0d9" _crossorigin "anonymous" ] []
_crossorigin "anonymous"
] []