From 68f05edc226e8cd925c3db35da0a65e28a2600f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Feb 2026 00:33:21 +0000 Subject: [PATCH 1/4] Rename Datastar attributes from _ds prefix to _data prefix The _ds shorthand was unclear. Renamed all Datastar attributes to use the _data prefix (e.g., _dsShow -> _dataShow, _dsBind -> _dataBind) for better consistency with the underlying data-* HTML attributes. Removed the generic _ds helper from Datastar since Html._data already provides the same functionality. https://claude.ai/code/session_019s59KCRtbdgh6H3YMXfTxj --- sln/src/Docs/docs/datastar.md | 100 +++++++++++++------------- sln/src/FSharp.ViewEngine/Datastar.fs | 74 +++++++++---------- 2 files changed, 85 insertions(+), 89 deletions(-) diff --git a/sln/src/Docs/docs/datastar.md b/sln/src/Docs/docs/datastar.md index 018d86c..b04e88f 100644 --- a/sln/src/Docs/docs/datastar.md +++ b/sln/src/Docs/docs/datastar.md @@ -16,12 +16,12 @@ open type Datastar ### data-* -Use `_ds` for any Datastar `data-*` attribute: +Use `_data` for any Datastar `data-*` attribute: ```fsharp div { - _ds ("star", "true") - _ds "loading" + _data ("star", "true") + _data "loading" } ``` @@ -33,8 +33,8 @@ Define reactive signals on an element: ```fsharp div { - _dsSignals ("count", "0") - _dsSignals ("name", "'World'") + _dataSignals ("count", "0") + _dataSignals ("name", "'World'") } ``` @@ -44,7 +44,7 @@ Listen for events and run expressions: ```fsharp button { - _dsOn ("click", "$count++") + _dataOn ("click", "$count++") "Increment" } ``` @@ -54,8 +54,8 @@ button { Two-way bind a signal to an input element: ```fsharp -input { _type "text"; _dsBind "name" } -input { _type "text"; _dsBind ("name", "value") } +input { _type "text"; _dataBind "name" } +input { _type "text"; _dataBind ("name", "value") } ``` ### data-show @@ -63,7 +63,7 @@ input { _type "text"; _dsBind ("name", "value") } Conditionally show or hide an element: ```fsharp -div { _dsShow "$count > 0"; "Count is positive" } +div { _dataShow "$count > 0"; "Count is positive" } ``` ### data-text @@ -71,7 +71,7 @@ div { _dsShow "$count > 0"; "Count is positive" } Set the text content of an element reactively: ```fsharp -span { _dsText "$count" } +span { _dataText "$count" } ``` ### data-effect @@ -79,7 +79,7 @@ span { _dsText "$count" } Run an expression whenever its dependencies change: ```fsharp -div { _dsEffect "console.log($count)" } +div { _dataEffect "console.log($count)" } ``` ### data-init @@ -87,7 +87,7 @@ div { _dsEffect "console.log($count)" } Run an expression when the element is initialized: ```fsharp -div { _dsInit "console.log('initialized')" } +div { _dataInit "console.log('initialized')" } ``` ### data-attr @@ -95,7 +95,7 @@ div { _dsInit "console.log('initialized')" } Dynamically set an HTML attribute: ```fsharp -div { _dsAttr ("disabled", "$count === 0") } +div { _dataAttr ("disabled", "$count === 0") } ``` ### data-class @@ -103,7 +103,7 @@ div { _dsAttr ("disabled", "$count === 0") } Toggle a CSS class based on an expression: ```fsharp -div { _dsClass ("active", "$isActive") } +div { _dataClass ("active", "$isActive") } ``` ### data-computed @@ -111,7 +111,7 @@ div { _dsClass ("active", "$isActive") } Define a computed signal derived from other signals: ```fsharp -div { _dsComputed ("double", "$count * 2") } +div { _dataComputed ("double", "$count * 2") } ``` ### data-style @@ -119,7 +119,7 @@ div { _dsComputed ("double", "$count * 2") } Dynamically set a CSS style property: ```fsharp -div { _dsStyle ("color", "$isError ? 'red' : 'green'") } +div { _dataStyle ("color", "$isError ? 'red' : 'green'") } ``` ### data-ref @@ -127,8 +127,8 @@ div { _dsStyle ("color", "$isError ? 'red' : 'green'") } Reference an element by name: ```fsharp -input { _dsRef "myInput" } -input { _dsRef ("myInput", "value") } +input { _dataRef "myInput" } +input { _dataRef ("myInput", "value") } ``` ### data-indicator @@ -136,8 +136,8 @@ input { _dsRef ("myInput", "value") } Bind a loading indicator signal: ```fsharp -button { _dsIndicator "loading" } -button { _dsIndicator ("loading", "true") } +button { _dataIndicator "loading" } +button { _dataIndicator ("loading", "true") } ``` ### data-json-signals @@ -145,8 +145,8 @@ button { _dsIndicator ("loading", "true") } Merge JSON signals into the signal store: ```fsharp -div { _dsJsonSignals """{"count": 0}""" } -div { _dsJsonSignals () } +div { _dataJsonSignals """{"count": 0}""" } +div { _dataJsonSignals () } ``` ### data-ignore @@ -154,7 +154,7 @@ div { _dsJsonSignals () } Prevent Datastar from processing an element: ```fsharp -div { _dsIgnore } +div { _dataIgnore } ``` ### data-ignore-morph @@ -162,7 +162,7 @@ div { _dsIgnore } Prevent morphing of an element during updates: ```fsharp -div { _dsIgnoreMorph } +div { _dataIgnoreMorph } ``` ### data-on-intersect @@ -170,7 +170,7 @@ div { _dsIgnoreMorph } Run an expression when an element enters the viewport: ```fsharp -div { _dsOnIntersect "$count++" } +div { _dataOnIntersect "$count++" } ``` ### data-on-interval @@ -178,7 +178,7 @@ div { _dsOnIntersect "$count++" } Run an expression on a timed interval: ```fsharp -div { _dsOnInterval "$count++" } +div { _dataOnInterval "$count++" } ``` ### data-on-signal-patch @@ -186,7 +186,7 @@ div { _dsOnInterval "$count++" } Run an expression when signals are patched: ```fsharp -div { _dsOnSignalPatch "console.log('patched')" } +div { _dataOnSignalPatch "console.log('patched')" } ``` ### data-on-signal-patch-filter @@ -194,7 +194,7 @@ div { _dsOnSignalPatch "console.log('patched')" } Filter which signal patches trigger the expression: ```fsharp -div { _dsOnSignalPatchFilter "count" } +div { _dataOnSignalPatchFilter "count" } ``` ### data-preserve-attr @@ -202,7 +202,7 @@ div { _dsOnSignalPatchFilter "count" } Preserve specified attributes during morphing: ```fsharp -div { _dsPreserveAttr "class" } +div { _dataPreserveAttr "class" } ``` ## Pro Attributes @@ -212,7 +212,7 @@ div { _dsPreserveAttr "class" } Apply animations to an element: ```fsharp -div { _dsAnimate "fadeIn 0.5s" } +div { _dataAnimate "fadeIn 0.5s" } ``` ### data-custom-validity @@ -220,7 +220,7 @@ div { _dsAnimate "fadeIn 0.5s" } Set custom validation messages: ```fsharp -input { _dsCustomValidity "$name === '' ? 'Name is required' : ''" } +input { _dataCustomValidity "$name === '' ? 'Name is required' : ''" } ``` ### data-on-raf @@ -228,7 +228,7 @@ input { _dsCustomValidity "$name === '' ? 'Name is required' : ''" } Run an expression on every animation frame: ```fsharp -canvas { _dsOnRaf "draw()" } +canvas { _dataOnRaf "draw()" } ``` ### data-on-resize @@ -236,7 +236,7 @@ canvas { _dsOnRaf "draw()" } Run an expression when the element is resized: ```fsharp -div { _dsOnResize "console.log('resized')" } +div { _dataOnResize "console.log('resized')" } ``` ### data-persist @@ -244,8 +244,8 @@ div { _dsOnResize "console.log('resized')" } Persist signals to local storage: ```fsharp -div { _dsPersist "count" } -div { _dsPersist ("count", "session") } +div { _dataPersist "count" } +div { _dataPersist ("count", "session") } ``` ### data-query-string @@ -253,8 +253,8 @@ div { _dsPersist ("count", "session") } Sync signals with URL query parameters: ```fsharp -div { _dsQueryString "count" } -div { _dsQueryString () } +div { _dataQueryString "count" } +div { _dataQueryString () } ``` ### data-replace-url @@ -262,7 +262,7 @@ div { _dsQueryString () } Replace the current URL: ```fsharp -div { _dsReplaceUrl "/new-path" } +div { _dataReplaceUrl "/new-path" } ``` ### data-rocket @@ -270,7 +270,7 @@ div { _dsReplaceUrl "/new-path" } Prefetch pages for instant navigation: ```fsharp -a { _dsRocket "true"; _href "/next-page"; "Next" } +a { _dataRocket "true"; _href "/next-page"; "Next" } ``` ### data-scroll-into-view @@ -278,7 +278,7 @@ a { _dsRocket "true"; _href "/next-page"; "Next" } Scroll the element into view: ```fsharp -div { _dsScrollIntoView } +div { _dataScrollIntoView } ``` ### data-view-transition @@ -286,7 +286,7 @@ div { _dsScrollIntoView } Apply view transitions: ```fsharp -div { _dsViewTransition "fade" } +div { _dataViewTransition "fade" } ``` ## Complete Example @@ -295,19 +295,19 @@ Here's a complete example combining multiple Datastar attributes: ```fsharp div { - _dsSignals ("count", "0") - _dsSignals ("name", "'World'") - _dsComputed ("greeting", "'Hello, ' + $name + '!'") + _dataSignals ("count", "0") + _dataSignals ("name", "'World'") + _dataComputed ("greeting", "'Hello, ' + $name + '!'") - input { _type "text"; _dsBind "name" } - span { _dsText "$greeting" } + input { _type "text"; _dataBind "name" } + span { _dataText "$greeting" } button { - _dsOn ("click", "$count++") - _dsClass ("active", "$count > 0") + _dataOn ("click", "$count++") + _dataClass ("active", "$count > 0") "Clicked " } - span { _dsText "$count" } - span { _dsShow "$count > 0"; " times" } + span { _dataText "$count" } + span { _dataShow "$count > 0"; " times" } } ``` diff --git a/sln/src/FSharp.ViewEngine/Datastar.fs b/sln/src/FSharp.ViewEngine/Datastar.fs index 258a260..e4e2ab8 100644 --- a/sln/src/FSharp.ViewEngine/Datastar.fs +++ b/sln/src/FSharp.ViewEngine/Datastar.fs @@ -1,45 +1,41 @@ namespace FSharp.ViewEngine type Datastar = - // Generic data-* attribute - static member inline _ds (key: string, value: string) = { Name = $"data-{key}"; Value = ValueSome value } - static member inline _ds (key: string) = { Name = $"data-{key}"; Value = ValueNone } - // Core attributes - static member inline _dsAttr (name: string, v: string) = { Name = $"data-attr:{name}"; Value = ValueSome v } - static member inline _dsBind (name: string) = { Name = $"data-bind:{name}"; Value = ValueNone } - static member inline _dsBind (name: string, v: string) = { Name = $"data-bind:{name}"; Value = ValueSome v } - static member inline _dsClass (name: string, v: string) = { Name = $"data-class:{name}"; Value = ValueSome v } - static member inline _dsComputed (name: string, v: string) = { Name = $"data-computed:{name}"; Value = ValueSome v } - static member inline _dsEffect (v: string) = { Name = "data-effect"; Value = ValueSome v } - static member inline _dsIgnore = { Name = "data-ignore"; Value = ValueNone } - static member inline _dsIgnoreMorph = { Name = "data-ignore-morph"; Value = ValueNone } - static member inline _dsIndicator (name: string) = { Name = $"data-indicator:{name}"; Value = ValueNone } - static member inline _dsIndicator (name: string, v: string) = { Name = $"data-indicator:{name}"; Value = ValueSome v } - static member inline _dsInit (v: string) = { Name = "data-init"; Value = ValueSome v } - static member inline _dsJsonSignals (?v: string) = match v with Some v -> { Name = "data-json-signals"; Value = ValueSome v } | None -> { Name = "data-json-signals"; Value = ValueNone } - static member inline _dsOn (event: string, v: string) = { Name = $"data-on:{event}"; Value = ValueSome v } - static member inline _dsOnIntersect (v: string) = { Name = "data-on-intersect"; Value = ValueSome v } - static member inline _dsOnInterval (v: string) = { Name = "data-on-interval"; Value = ValueSome v } - static member inline _dsOnSignalPatch (v: string) = { Name = "data-on-signal-patch"; Value = ValueSome v } - static member inline _dsOnSignalPatchFilter (v: string) = { Name = "data-on-signal-patch-filter"; Value = ValueSome v } - static member inline _dsPreserveAttr (v: string) = { Name = "data-preserve-attr"; Value = ValueSome v } - static member inline _dsRef (name: string) = { Name = $"data-ref:{name}"; Value = ValueNone } - static member inline _dsRef (name: string, v: string) = { Name = $"data-ref:{name}"; Value = ValueSome v } - static member inline _dsShow (v: string) = { Name = "data-show"; Value = ValueSome v } - static member inline _dsSignals (name: string, v: string) = { Name = $"data-signals:{name}"; Value = ValueSome v } - static member inline _dsStyle (prop: string, v: string) = { Name = $"data-style:{prop}"; Value = ValueSome v } - static member inline _dsText (v: string) = { Name = "data-text"; Value = ValueSome v } + static member inline _dataAttr (name: string, v: string) = { Name = $"data-attr:{name}"; Value = ValueSome v } + static member inline _dataBind (name: string) = { Name = $"data-bind:{name}"; Value = ValueNone } + static member inline _dataBind (name: string, v: string) = { Name = $"data-bind:{name}"; Value = ValueSome v } + static member inline _dataClass (name: string, v: string) = { Name = $"data-class:{name}"; Value = ValueSome v } + static member inline _dataComputed (name: string, v: string) = { Name = $"data-computed:{name}"; Value = ValueSome v } + static member inline _dataEffect (v: string) = { Name = "data-effect"; Value = ValueSome v } + static member inline _dataIgnore = { Name = "data-ignore"; Value = ValueNone } + static member inline _dataIgnoreMorph = { Name = "data-ignore-morph"; Value = ValueNone } + static member inline _dataIndicator (name: string) = { Name = $"data-indicator:{name}"; Value = ValueNone } + static member inline _dataIndicator (name: string, v: string) = { Name = $"data-indicator:{name}"; Value = ValueSome v } + static member inline _dataInit (v: string) = { Name = "data-init"; Value = ValueSome v } + static member inline _dataJsonSignals (?v: string) = match v with Some v -> { Name = "data-json-signals"; Value = ValueSome v } | None -> { Name = "data-json-signals"; Value = ValueNone } + static member inline _dataOn (event: string, v: string) = { Name = $"data-on:{event}"; Value = ValueSome v } + static member inline _dataOnIntersect (v: string) = { Name = "data-on-intersect"; Value = ValueSome v } + static member inline _dataOnInterval (v: string) = { Name = "data-on-interval"; Value = ValueSome v } + static member inline _dataOnSignalPatch (v: string) = { Name = "data-on-signal-patch"; Value = ValueSome v } + static member inline _dataOnSignalPatchFilter (v: string) = { Name = "data-on-signal-patch-filter"; Value = ValueSome v } + static member inline _dataPreserveAttr (v: string) = { Name = "data-preserve-attr"; Value = ValueSome v } + static member inline _dataRef (name: string) = { Name = $"data-ref:{name}"; Value = ValueNone } + static member inline _dataRef (name: string, v: string) = { Name = $"data-ref:{name}"; Value = ValueSome v } + static member inline _dataShow (v: string) = { Name = "data-show"; Value = ValueSome v } + static member inline _dataSignals (name: string, v: string) = { Name = $"data-signals:{name}"; Value = ValueSome v } + static member inline _dataStyle (prop: string, v: string) = { Name = $"data-style:{prop}"; Value = ValueSome v } + static member inline _dataText (v: string) = { Name = "data-text"; Value = ValueSome v } // Pro attributes - static member inline _dsAnimate (v: string) = { Name = "data-animate"; Value = ValueSome v } - static member inline _dsCustomValidity (v: string) = { Name = "data-custom-validity"; Value = ValueSome v } - static member inline _dsOnRaf (v: string) = { Name = "data-on-raf"; Value = ValueSome v } - static member inline _dsOnResize (v: string) = { Name = "data-on-resize"; Value = ValueSome v } - static member inline _dsPersist (key: string) = { Name = $"data-persist:{key}"; Value = ValueNone } - static member inline _dsPersist (key: string, v: string) = { Name = $"data-persist:{key}"; Value = ValueSome v } - static member inline _dsQueryString (?v: string) = match v with Some v -> { Name = "data-query-string"; Value = ValueSome v } | None -> { Name = "data-query-string"; Value = ValueNone } - static member inline _dsReplaceUrl (v: string) = { Name = "data-replace-url"; Value = ValueSome v } - static member inline _dsRocket (v: string) = { Name = "data-rocket"; Value = ValueSome v } - static member inline _dsScrollIntoView = { Name = "data-scroll-into-view"; Value = ValueNone } - static member inline _dsViewTransition (v: string) = { Name = "data-view-transition"; Value = ValueSome v } + static member inline _dataAnimate (v: string) = { Name = "data-animate"; Value = ValueSome v } + static member inline _dataCustomValidity (v: string) = { Name = "data-custom-validity"; Value = ValueSome v } + static member inline _dataOnRaf (v: string) = { Name = "data-on-raf"; Value = ValueSome v } + static member inline _dataOnResize (v: string) = { Name = "data-on-resize"; Value = ValueSome v } + static member inline _dataPersist (key: string) = { Name = $"data-persist:{key}"; Value = ValueNone } + static member inline _dataPersist (key: string, v: string) = { Name = $"data-persist:{key}"; Value = ValueSome v } + static member inline _dataQueryString (?v: string) = match v with Some v -> { Name = "data-query-string"; Value = ValueSome v } | None -> { Name = "data-query-string"; Value = ValueNone } + static member inline _dataReplaceUrl (v: string) = { Name = "data-replace-url"; Value = ValueSome v } + static member inline _dataRocket (v: string) = { Name = "data-rocket"; Value = ValueSome v } + static member inline _dataScrollIntoView = { Name = "data-scroll-into-view"; Value = ValueNone } + static member inline _dataViewTransition (v: string) = { Name = "data-view-transition"; Value = ValueSome v } From 10bcfced61eea040584db532244a8a1774df3355 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Feb 2026 00:42:21 +0000 Subject: [PATCH 2/4] Add Datastar attribute tests using new _data prefix Adds test coverage for all renamed Datastar attributes, verifying they render the correct data-* HTML attributes. Also adds `open type Datastar` to the test module imports. https://claude.ai/code/session_019s59KCRtbdgh6H3YMXfTxj --- sln/src/Tests/Tests.fs | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/sln/src/Tests/Tests.fs b/sln/src/Tests/Tests.fs index 097b0d2..f50a5da 100644 --- a/sln/src/Tests/Tests.fs +++ b/sln/src/Tests/Tests.fs @@ -9,6 +9,7 @@ open Expecto open type Html open type Htmx open type Alpine +open type Datastar open type Svg open type Tailwind @@ -145,6 +146,49 @@ let tests = Expect.equal (String.clean actual) (String.clean expectedHtml) "Rendered HTML should match expected" } + test "Datastar attributes should render with data- prefix" { + let actual = + div { + _dataSignals ("count", "0") + _dataOn ("click", "$count++") + _dataShow "$count > 0" + _dataText "$count" + _dataBind "name" + _dataEffect "console.log($count)" + _dataClass ("active", "$isActive") + _dataAttr ("disabled", "$count === 0") + _dataComputed ("double", "$count * 2") + _dataInit "console.log('init')" + _dataIgnore + _dataIgnoreMorph + _dataStyle ("color", "red") + _dataRef "myInput" + _dataIndicator "loading" + _dataAnimate "fadeIn" + _dataPersist "count" + _dataScrollIntoView + "Content" + } |> Render.toString + Expect.stringContains actual """data-signals:count="0"""" "data-signals" + Expect.stringContains actual """data-on:click="$count++"""" "data-on" + Expect.stringContains actual """data-show="$count > 0"""" "data-show" + Expect.stringContains actual """data-text="$count"""" "data-text" + Expect.stringContains actual "data-bind:name" "data-bind" + Expect.stringContains actual """data-effect="console.log($count)"""" "data-effect" + Expect.stringContains actual """data-class:active="$isActive"""" "data-class" + Expect.stringContains actual """data-attr:disabled="$count === 0"""" "data-attr" + Expect.stringContains actual """data-computed:double="$count * 2"""" "data-computed" + Expect.stringContains actual """data-init="console.log('init')"""" "data-init" + Expect.stringContains actual "data-ignore-morph" "data-ignore-morph" + Expect.stringContains actual "data-ignore" "data-ignore" + Expect.stringContains actual """data-style:color="red"""" "data-style" + Expect.stringContains actual "data-ref:myInput" "data-ref" + Expect.stringContains actual "data-indicator:loading" "data-indicator" + Expect.stringContains actual """data-animate="fadeIn"""" "data-animate" + Expect.stringContains actual "data-persist:count" "data-persist" + Expect.stringContains actual "data-scroll-into-view" "data-scroll-into-view" + } + test "HtmlEncode should match HttpUtility.HtmlEncode" { let inputs = [ From e6846bf2a7d44e1ead6d67697b5b3f78767b1539 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Feb 2026 00:53:39 +0000 Subject: [PATCH 3/4] Fix triple-quoted string syntax errors in Datastar test assertions F# triple-quoted strings cannot end with a `"` character before the closing `"""`, as the parser greedily matches the first `"""` it finds. Switched to regular escaped strings for assertions containing quoted attribute values. https://claude.ai/code/session_019s59KCRtbdgh6H3YMXfTxj --- sln/src/Tests/Tests.fs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sln/src/Tests/Tests.fs b/sln/src/Tests/Tests.fs index f50a5da..5f4389a 100644 --- a/sln/src/Tests/Tests.fs +++ b/sln/src/Tests/Tests.fs @@ -169,22 +169,22 @@ let tests = _dataScrollIntoView "Content" } |> Render.toString - Expect.stringContains actual """data-signals:count="0"""" "data-signals" - Expect.stringContains actual """data-on:click="$count++"""" "data-on" - Expect.stringContains actual """data-show="$count > 0"""" "data-show" - Expect.stringContains actual """data-text="$count"""" "data-text" + Expect.stringContains actual "data-signals:count=\"0\"" "data-signals" + Expect.stringContains actual "data-on:click=\"$count++\"" "data-on" + Expect.stringContains actual "data-show=\"$count > 0\"" "data-show" + Expect.stringContains actual "data-text=\"$count\"" "data-text" Expect.stringContains actual "data-bind:name" "data-bind" - Expect.stringContains actual """data-effect="console.log($count)"""" "data-effect" - Expect.stringContains actual """data-class:active="$isActive"""" "data-class" - Expect.stringContains actual """data-attr:disabled="$count === 0"""" "data-attr" - Expect.stringContains actual """data-computed:double="$count * 2"""" "data-computed" - Expect.stringContains actual """data-init="console.log('init')"""" "data-init" + Expect.stringContains actual "data-effect=\"console.log($count)\"" "data-effect" + Expect.stringContains actual "data-class:active=\"$isActive\"" "data-class" + Expect.stringContains actual "data-attr:disabled=\"$count === 0\"" "data-attr" + Expect.stringContains actual "data-computed:double=\"$count * 2\"" "data-computed" + Expect.stringContains actual "data-init=\"console.log('init')\"" "data-init" Expect.stringContains actual "data-ignore-morph" "data-ignore-morph" Expect.stringContains actual "data-ignore" "data-ignore" - Expect.stringContains actual """data-style:color="red"""" "data-style" + Expect.stringContains actual "data-style:color=\"red\"" "data-style" Expect.stringContains actual "data-ref:myInput" "data-ref" Expect.stringContains actual "data-indicator:loading" "data-indicator" - Expect.stringContains actual """data-animate="fadeIn"""" "data-animate" + Expect.stringContains actual "data-animate=\"fadeIn\"" "data-animate" Expect.stringContains actual "data-persist:count" "data-persist" Expect.stringContains actual "data-scroll-into-view" "data-scroll-into-view" } From 9fe6ff9d432909ae3951f066bd0960a5c900aa09 Mon Sep 17 00:00:00 2001 From: Andrew Meier Date: Sat, 28 Feb 2026 04:33:29 -0500 Subject: [PATCH 4/4] Update Datastar docs and add data-persist helper --- sln/src/Docs/docs/datastar.md | 37 ++++++++------------------- sln/src/FSharp.ViewEngine/Datastar.fs | 1 + sln/src/Tests/Tests.fs | 3 ++- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/sln/src/Docs/docs/datastar.md b/sln/src/Docs/docs/datastar.md index b04e88f..62f9a88 100644 --- a/sln/src/Docs/docs/datastar.md +++ b/sln/src/Docs/docs/datastar.md @@ -12,19 +12,6 @@ open type Html open type Datastar ``` -## Generic Attribute - -### data-* - -Use `_data` for any Datastar `data-*` attribute: - -```fsharp -div { - _data ("star", "true") - _data "loading" -} -``` - ## Core Attributes ### data-signals @@ -55,7 +42,6 @@ Two-way bind a signal to an input element: ```fsharp input { _type "text"; _dataBind "name" } -input { _type "text"; _dataBind ("name", "value") } ``` ### data-show @@ -128,7 +114,6 @@ Reference an element by name: ```fsharp input { _dataRef "myInput" } -input { _dataRef ("myInput", "value") } ``` ### data-indicator @@ -137,16 +122,15 @@ Bind a loading indicator signal: ```fsharp button { _dataIndicator "loading" } -button { _dataIndicator ("loading", "true") } ``` ### data-json-signals -Merge JSON signals into the signal store: +Render signals as JSON for debugging: ```fsharp -div { _dataJsonSignals """{"count": 0}""" } -div { _dataJsonSignals () } +pre { _dataJsonSignals () } +pre { _dataJsonSignals "{include: /counter/, exclude: /temp$/}" } ``` ### data-ignore @@ -194,7 +178,7 @@ div { _dataOnSignalPatch "console.log('patched')" } Filter which signal patches trigger the expression: ```fsharp -div { _dataOnSignalPatchFilter "count" } +div { _dataOnSignalPatchFilter "{include: /^count$/}" } ``` ### data-preserve-attr @@ -241,11 +225,12 @@ div { _dataOnResize "console.log('resized')" } ### data-persist -Persist signals to local storage: +Persist signals to local storage (or session storage with modifiers): ```fsharp -div { _dataPersist "count" } -div { _dataPersist ("count", "session") } +div { _dataPersist () } // default key: datastar +div { _dataPersist "mykey" } // custom storage key +div { _dataPersist ("mykey", "{include: /foo/}") } // key + filter object ``` ### data-query-string @@ -253,8 +238,8 @@ div { _dataPersist ("count", "session") } Sync signals with URL query parameters: ```fsharp -div { _dataQueryString "count" } div { _dataQueryString () } +div { _dataQueryString "{include: /foo/, exclude: /temp$/}" } ``` ### data-replace-url @@ -267,10 +252,10 @@ div { _dataReplaceUrl "/new-path" } ### data-rocket -Prefetch pages for instant navigation: +Create a Rocket web component: ```fsharp -a { _dataRocket "true"; _href "/next-page"; "Next" } +div { _dataRocket "{ endpoint: '/stream' }" } ``` ### data-scroll-into-view diff --git a/sln/src/FSharp.ViewEngine/Datastar.fs b/sln/src/FSharp.ViewEngine/Datastar.fs index e4e2ab8..4f910c5 100644 --- a/sln/src/FSharp.ViewEngine/Datastar.fs +++ b/sln/src/FSharp.ViewEngine/Datastar.fs @@ -32,6 +32,7 @@ type Datastar = static member inline _dataCustomValidity (v: string) = { Name = "data-custom-validity"; Value = ValueSome v } static member inline _dataOnRaf (v: string) = { Name = "data-on-raf"; Value = ValueSome v } static member inline _dataOnResize (v: string) = { Name = "data-on-resize"; Value = ValueSome v } + static member inline _dataPersist () = { Name = "data-persist"; Value = ValueNone } static member inline _dataPersist (key: string) = { Name = $"data-persist:{key}"; Value = ValueNone } static member inline _dataPersist (key: string, v: string) = { Name = $"data-persist:{key}"; Value = ValueSome v } static member inline _dataQueryString (?v: string) = match v with Some v -> { Name = "data-query-string"; Value = ValueSome v } | None -> { Name = "data-query-string"; Value = ValueNone } diff --git a/sln/src/Tests/Tests.fs b/sln/src/Tests/Tests.fs index 5f4389a..ecae26d 100644 --- a/sln/src/Tests/Tests.fs +++ b/sln/src/Tests/Tests.fs @@ -165,6 +165,7 @@ let tests = _dataRef "myInput" _dataIndicator "loading" _dataAnimate "fadeIn" + _dataPersist () _dataPersist "count" _dataScrollIntoView "Content" @@ -185,7 +186,7 @@ let tests = Expect.stringContains actual "data-ref:myInput" "data-ref" Expect.stringContains actual "data-indicator:loading" "data-indicator" Expect.stringContains actual "data-animate=\"fadeIn\"" "data-animate" - Expect.stringContains actual "data-persist:count" "data-persist" + Expect.stringContains actual "data-persist data-persist:count" "data-persist" Expect.stringContains actual "data-scroll-into-view" "data-scroll-into-view" }