defmodule Nex.Datastar do
@moduledoc """
Server-side helpers for the Datastar hypermedia framework.
Produces SSE event maps compatible with `Nex.stream/1` that follow
the Datastar wire protocol.
## Usage
Nex.stream(fn send ->
send.(Nex.Datastar.patch_elements(~s(
Hello
)))
send.(Nex.Datastar.patch_signals(%{count: 1}))
end)
"""
@doc """
Builds a `datastar-patch-elements` SSE event.
Returns a map that can be passed directly to the `send` function
inside `Nex.stream/1`.
## Options
* `:selector` - CSS selector for the target element. If omitted,
Datastar uses the fragment's `id` attribute.
* `:mode` - Merge mode. One of `"morph"`, `"inner"`, `"outer"`,
`"prepend"`, `"append"`, `"before"`, `"after"`, `"upsertAttributes"`.
* `:use_view_transition` - Whether to use view transitions. Defaults to `false`.
## Examples
Nex.Datastar.patch_elements(~s(42
))
#=> %{event: "datastar-patch-elements", data: "fragments 42
"}
Nex.Datastar.patch_elements("hi", selector: "#feed", mode: "append")
"""
def patch_elements(fragments, opts \\ []) do
lines =
[
if(opts[:selector], do: "selector #{opts[:selector]}"),
if(opts[:mode], do: "mode #{opts[:mode]}"),
if(opts[:use_view_transition], do: "useViewTransition true"),
"fragments #{fragments}"
]
|> Enum.reject(&is_nil/1)
%{event: "datastar-patch-elements", data: Enum.join(lines, "\n")}
end
@doc """
Builds a `datastar-patch-signals` SSE event.
Signals are encoded as JSON following RFC 7396 (JSON Merge Patch).
## Options
* `:only_if_missing` - Only set signals that don't already exist
on the client. Defaults to `false`.
## Examples
Nex.Datastar.patch_signals(%{count: 1, name: "Nex"})
#=> %{event: "datastar-patch-signals", data: "signals {\\"count\\":1,\\"name\\":\\"Nex\\"}"}
"""
def patch_signals(signals, opts \\ []) do
json = if is_binary(signals), do: signals, else: Jason.encode!(signals)
lines =
[
if(opts[:only_if_missing], do: "onlyIfMissing true"),
"signals #{json}"
]
|> Enum.reject(&is_nil/1)
%{event: "datastar-patch-signals", data: Enum.join(lines, "\n")}
end
end