diff --git a/core/src/scalive/HtmlElement.scala b/core/src/scalive/HtmlElement.scala
index 9ccb1ba..ea0d485 100644
--- a/core/src/scalive/HtmlElement.scala
+++ b/core/src/scalive/HtmlElement.scala
@@ -64,10 +64,10 @@ class HtmlAttr[V](val name: String, val codec: Codec[V, String]):
class HtmlAttrJsonValue(val name: String):
- def :=[V: JsonCodec](value: V): Mod.Attr =
+ def :=[V: JsonEncoder](value: V): Mod.Attr =
Mod.Attr.Static(name, value.toJson, isJson = true)
- def :=[V: JsonCodec](value: Dyn[V]): Mod.Attr =
+ def :=[V: JsonEncoder](value: Dyn[V]): Mod.Attr =
Mod.Attr.Dyn(name, value(_.toJson), isJson = true)
sealed trait Mod
diff --git a/core/src/scalive/JS.scala b/core/src/scalive/JS.scala
new file mode 100644
index 0000000..fed1f4b
--- /dev/null
+++ b/core/src/scalive/JS.scala
@@ -0,0 +1,53 @@
+package scalive
+
+import zio.json.*
+import zio.json.ast.Json
+
+val JS: JSCommands.JSCommand = JSCommands.empty
+
+object JSCommands:
+ opaque type JSCommand = List[Json]
+
+ def empty: JSCommand = List.empty
+
+ object JSCommand:
+ given JsonEncoder[JSCommand] =
+ JsonEncoder[Json].contramap(ops => Json.Arr(ops.reverse*))
+
+ private def classNames(names: String) = names.split("\\s+")
+
+ extension (ops: JSCommand)
+ private def addOp[A: JsonEncoder](kind: String, args: A) =
+ (kind, args).toJsonAST.getOrElse(throw new IllegalArgumentException()) :: ops
+
+ def toggleClass(
+ names: String,
+ to: String = "",
+ transition: String | (String, String, String) = "",
+ time: Int = 200,
+ blocking: Boolean = true
+ ) =
+ ops.addOp(
+ "toggle_class",
+ Args.ToggleClass(
+ classNames(names),
+ Some(to).filterNot(_.isBlank),
+ transition match
+ case "" => None
+ case names: String => Some(classNames(names))
+ case t: (String, String, String) => Some(t.toList),
+ Some(time).filterNot(_ == 200),
+ Some(blocking).filterNot(_ == true)
+ )
+ )
+
+ private object Args:
+ final case class ToggleClass(
+ names: Seq[String],
+ to: Option[String],
+ transition: Option[Seq[String]],
+ time: Option[Int],
+ blocking: Option[Boolean])
+ derives JsonEncoder
+
+end JSCommands
diff --git a/example/src/HomeLiveView.scala b/example/src/HomeLiveView.scala
index 7b5f984..241f02a 100644
--- a/example/src/HomeLiveView.scala
+++ b/example/src/HomeLiveView.scala
@@ -4,8 +4,8 @@ import zio.stream.ZStream
class HomeLiveView() extends LiveView[String, Unit]:
val links = List(
- "/counter" -> "Counter",
- "/list" -> "List"
+ "/counter" -> "Counter",
+ "/list?q=test" -> "List"
)
def init = ZIO.succeed(())
diff --git a/example/src/ListLiveView.scala b/example/src/ListLiveView.scala
index 9beb8d8..6d92063 100644
--- a/example/src/ListLiveView.scala
+++ b/example/src/ListLiveView.scala
@@ -54,7 +54,11 @@ class ListLiveView(someParam: String) extends LiveView[Msg, Model]:
phx.click := Msg.IncAge(1),
"Inc age"
),
- span(cls := "grow")
+ span(cls := "grow"),
+ button(
+ phx.click := JS.toggleClass("bg-red-500 border-5"),
+ "Toggle color"
+ )
)
)