From c96718ba1e5f63c54837aa9a78772059040aa71a Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Sun, 4 Dec 2022 20:39:47 +0100 Subject: [PATCH 01/85] Add data prop --- calico/src/main/scala/calico/html.scala | 37 +++++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 15bac455..8198ce99 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -19,9 +19,6 @@ package html import calico.syntax.* import calico.util.DomHotswap -import cats.Foldable -import cats.Hash -import cats.Monad import cats.effect.IO import cats.effect.kernel.Async import cats.effect.kernel.Ref @@ -29,6 +26,9 @@ import cats.effect.kernel.Resource import cats.effect.kernel.Sync import cats.effect.std.Dispatcher import cats.effect.syntax.all.* +import cats.Foldable +import cats.Hash +import cats.Monad import cats.syntax.all.* import com.raquo.domtypes.generic.builders.EventPropBuilder import com.raquo.domtypes.generic.builders.HtmlAttrBuilder @@ -42,16 +42,16 @@ import com.raquo.domtypes.generic.defs.props.* import com.raquo.domtypes.generic.defs.reflectedAttrs.* import com.raquo.domtypes.generic.defs.tags.* import com.raquo.domtypes.jsdom.defs.eventProps.* -import fs2.Pipe -import fs2.Stream import fs2.concurrent.Channel import fs2.concurrent.Signal +import fs2.Pipe +import fs2.Stream import org.scalajs.dom import shapeless3.deriving.K0 import scala.collection.mutable -import scala.scalajs.js import scala.collection.mutable.ListBuffer +import scala.scalajs.js object io extends Html[IO] @@ -174,6 +174,7 @@ trait HtmlBuilders[F[_]](using F: Async[F]) HtmlAttrModifiers[F], PropModifiers[F], ClassPropModifiers[F], + DataPropModifiers[F], EventPropModifiers[F], ChildrenModifiers[F], KeyedChildrenModifiers[F]: @@ -199,6 +200,8 @@ trait HtmlBuilders[F[_]](using F: Async[F]) def cls: ClassProp[F] = ClassProp[F] + def data(suffix: String): DataProp[F] = DataProp[F](suffix) + def children: Children[F] = Children[F] def children[K](f: K => Resource[F, fs2.dom.Node[F]]): KeyedChildren[F, K] = @@ -499,6 +502,28 @@ trait ClassPropModifiers[F[_]](using F: Async[F]): private val _forConstantClassProp: Modifier[F, Any, SingleConstantModifier] = (m, n) => Resource.eval(F.delay(n.asInstanceOf[js.Dictionary[String]]("className") = m.cls)) +final class DataProp[F[_]] private[calico] (suffix: String) + extends Prop[F, DataProp.SingleConstantModifier, String]( + s"data-$suffix", + new: + def decode(domValue: String) = DataProp.SingleConstantModifier(suffix, domValue) + + def encode(scalaValue: DataProp.SingleConstantModifier) = scalaValue.value + ): + import DataProp.* + + inline def :=(value: String): SingleConstantModifier = + SingleConstantModifier(suffix, value) + +object DataProp: + final class SingleConstantModifier(val suffix: String, val value: String) + +trait DataPropModifiers[F[_]](using F: Async[F]): + import DataProp.* + inline given forConstantDataProp[N <: fs2.dom.HtmlElement[F]] + : Modifier[F, N, SingleConstantModifier] = (m, n) => + Resource.eval(F.delay(n.asInstanceOf[dom.HTMLElement].dataset(m.suffix) = m.value)) + final class Children[F[_]] private[calico]: import Children.* From cf6efc96b8353a87491bbbb9fc08dc225614be72 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Mon, 5 Dec 2022 08:06:30 +0100 Subject: [PATCH 02/85] Correct property name --- calico/src/main/scala/calico/html.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 8198ce99..fa2e6bb7 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -504,7 +504,7 @@ trait ClassPropModifiers[F[_]](using F: Async[F]): final class DataProp[F[_]] private[calico] (suffix: String) extends Prop[F, DataProp.SingleConstantModifier, String]( - s"data-$suffix", + "dataset", new: def decode(domValue: String) = DataProp.SingleConstantModifier(suffix, domValue) From fcf41a429d086b632fb7416c60a4701da4d4898f Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Tue, 6 Dec 2022 20:48:23 +0100 Subject: [PATCH 03/85] Reuse SignalModifier implementation --- calico/src/main/scala/calico/html.scala | 99 +++++++++++++++++-------- 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index fa2e6bb7..63a8dcef 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -243,6 +243,15 @@ trait Modifier[F[_], E, A]: inline final def contramap[B](inline f: B => A): Modifier[F, E, B] = (b: B, e: E) => outer.modify(f(b), e) +object Modifier: + def forSignal[F[_]: Async, A, B, C](setter: (A, B, C) => F[Unit])( + signal: B => Signal[F, C]): Modifier[F, A, B] = (m, n) => + signal(m).getAndUpdates.flatMap { (head, tail) => + def set(v: C) = setter(n, m, v) + Resource.eval(set(head)) *> + tail.foreach(set(_)).compile.drain.cedeBackground.void + } + trait Modifiers[F[_]](using F: Async[F]): inline given forUnit[E]: Modifier[F, E, Unit] = _forUnit.asInstanceOf[Modifier[F, E, Unit]] @@ -423,7 +432,11 @@ object Prop: trait PropModifiers[F[_]](using F: Async[F]): import Prop.* - private inline def setProp[N, V, J](node: N, value: V, name: String, codec: Codec[V, J]) = + private[calico] inline def setProp[N, V, J]( + node: N, + value: V, + name: String, + codec: Codec[V, J]) = F.delay(node.asInstanceOf[js.Dictionary[J]](name) = codec.encode(value)) inline given forConstantProp[N, V, J]: Modifier[F, N, ConstantModifier[V, J]] = @@ -435,27 +448,21 @@ trait PropModifiers[F[_]](using F: Async[F]): inline given forSignalProp[N, V, J]: Modifier[F, N, SignalModifier[F, V, J]] = _forSignalProp.asInstanceOf[Modifier[F, N, SignalModifier[F, V, J]]] - private val _forSignalProp: Modifier[F, Any, SignalModifier[F, Any, Any]] = (m, n) => - m.values.getAndUpdates.flatMap { (head, tail) => - def set(v: Any) = setProp(n, v, m.name, m.codec) - Resource.eval(set(head)) *> - tail.foreach(set(_)).compile.drain.cedeBackground.void - } + private val _forSignalProp: Modifier[F, Any, SignalModifier[F, Any, Any]] = + Modifier.forSignal[F, Any, SignalModifier[F, Any, Any], Any]((any, m, v) => + setProp(any, v, m.name, m.codec))(_.values) inline given forOptionSignalProp[N, V, J]: Modifier[F, N, OptionSignalModifier[F, V, J]] = _forOptionSignalProp.asInstanceOf[Modifier[F, N, OptionSignalModifier[F, V, J]]] private val _forOptionSignalProp: Modifier[F, Any, OptionSignalModifier[F, Any, Any]] = - (m, n) => - m.values.getAndUpdates.flatMap { (head, tail) => - def set(v: Option[Any]) = F.delay { - val dict = n.asInstanceOf[js.Dictionary[Any]] - v.fold(dict -= m.name)(v => dict(m.name) = m.codec.encode(v)) + Modifier.forSignal[F, Any, OptionSignalModifier[F, Any, Any], Option[Any]]( + (any, osm, oany) => + F.delay { + val dict = any.asInstanceOf[js.Dictionary[Any]] + oany.fold(dict -= osm.name)(v => dict(osm.name) = osm.codec.encode(v)) () - } - Resource.eval(set(head)) *> - tail.foreach(set(_)).compile.drain.cedeBackground.void - } + })(_.values) final class EventProp[F[_], E] private[calico] (key: String): import EventProp.* @@ -502,27 +509,61 @@ trait ClassPropModifiers[F[_]](using F: Async[F]): private val _forConstantClassProp: Modifier[F, Any, SingleConstantModifier] = (m, n) => Resource.eval(F.delay(n.asInstanceOf[js.Dictionary[String]]("className") = m.cls)) -final class DataProp[F[_]] private[calico] (suffix: String) - extends Prop[F, DataProp.SingleConstantModifier, String]( - "dataset", - new: - def decode(domValue: String) = DataProp.SingleConstantModifier(suffix, domValue) - - def encode(scalaValue: DataProp.SingleConstantModifier) = scalaValue.value - ): +final class DataProp[F[_]] private[calico] (name: String): import DataProp.* - inline def :=(value: String): SingleConstantModifier = - SingleConstantModifier(suffix, value) + inline def :=(v: String): ConstantModifier = + ConstantModifier(name, v) + + inline def <--(vs: Signal[F, String]): SignalModifier[F] = + SignalModifier(name, vs) + + inline def <--(vs: Signal[F, Option[String]]): OptionSignalModifier[F] = + OptionSignalModifier(name, vs) object DataProp: - final class SingleConstantModifier(val suffix: String, val value: String) + final class ConstantModifier( + val name: String, + val value: String + ) + + final class SignalModifier[F[_]]( + val name: String, + val values: Signal[F, String] + ) + + final class OptionSignalModifier[F[_]]( + val name: String, + val values: Signal[F, Option[String]] + ) trait DataPropModifiers[F[_]](using F: Async[F]): import DataProp.* + + private inline def setDataProp[N](node: N, value: String, name: String) = + F.delay(node.asInstanceOf[dom.HTMLElement].dataset(name) = value) + inline given forConstantDataProp[N <: fs2.dom.HtmlElement[F]] - : Modifier[F, N, SingleConstantModifier] = (m, n) => - Resource.eval(F.delay(n.asInstanceOf[dom.HTMLElement].dataset(m.suffix) = m.value)) + : Modifier[F, N, ConstantModifier] = + _forConstantDataProp.asInstanceOf[Modifier[F, N, ConstantModifier]] + + private val _forConstantDataProp: Modifier[F, fs2.dom.HtmlElement[F], ConstantModifier] = + (m, n) => Resource.eval(setDataProp(n, m.value, m.name)) + + private val _forSignalDataProp: Modifier[F, Any, SignalModifier[F]] = + Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => + setDataProp(any, s, sm.name))(_.values) + + inline given forOptionSignalDataProp[N]: Modifier[F, N, OptionSignalModifier[F]] = + _forOptionSignalDataProp.asInstanceOf[Modifier[F, N, OptionSignalModifier[F]]] + + private val _forOptionSignalDataProp: Modifier[F, Any, OptionSignalModifier[F]] = + Modifier.forSignal[F, Any, OptionSignalModifier[F], Option[String]]((any, osm, os) => + F.delay { + val e = any.asInstanceOf[dom.HTMLElement] + os.fold(e.dataset -= osm.name)(v => e.dataset(osm.name) = v) + () + })(_.values) final class Children[F[_]] private[calico]: import Children.* From 69921b420d625f085ada0a244f3a460fbe3cf7ba Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Tue, 6 Dec 2022 20:53:30 +0100 Subject: [PATCH 04/85] Consistent value names --- calico/src/main/scala/calico/html.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 63a8dcef..a7d45030 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -245,9 +245,9 @@ trait Modifier[F[_], E, A]: object Modifier: def forSignal[F[_]: Async, A, B, C](setter: (A, B, C) => F[Unit])( - signal: B => Signal[F, C]): Modifier[F, A, B] = (m, n) => - signal(m).getAndUpdates.flatMap { (head, tail) => - def set(v: C) = setter(n, m, v) + signal: B => Signal[F, C]): Modifier[F, A, B] = (b, a) => + signal(b).getAndUpdates.flatMap { (head, tail) => + def set(c: C) = setter(a, b, c) Resource.eval(set(head)) *> tail.foreach(set(_)).compile.drain.cedeBackground.void } From 9aa791775feaf987cb69f9e38ce6fa1c6bd2de41 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Wed, 7 Dec 2022 20:05:55 +0100 Subject: [PATCH 05/85] Add Aria Attrs --- calico/src/main/scala/calico/html.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index a7d45030..8268a56b 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -49,14 +49,17 @@ import fs2.Stream import org.scalajs.dom import shapeless3.deriving.K0 +import com.raquo.domtypes.generic.defs.attrs.AriaAttrs import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.scalajs.js -object io extends Html[IO] +object io extends Html[IO]: + override val aria: Aria[IO] = Aria[IO] object Html: - def apply[F[_]: Async]: Html[F] = new Html[F] {} + def apply[F[_]: Async]: Html[F] = new: + override val aria: Aria[F] = Aria[F] trait Html[F[_]] extends HtmlBuilders[F], @@ -162,7 +165,13 @@ trait Html[F[_]] MediaEventProps[EventProp[F, _]], MiscellaneousEventProps[EventProp[F, _]], MouseEventProps[EventProp[F, _]], - PointerEventProps[EventProp[F, _]] + PointerEventProps[EventProp[F, _]]: + def aria: Aria[F] + +object Aria: + def apply[F[_]: Async]: Aria[F] = new Aria[F] {} + +trait Aria[F[_]] extends HtmlBuilders[F], AriaAttrs[HtmlAttr[F, _]] trait HtmlBuilders[F[_]](using F: Async[F]) extends HtmlTagBuilder[HtmlTagT[F], fs2.dom.HtmlElement[F]], From fd5c1016a47ca7830c3dc69df204c7fbe7f95303 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Fri, 9 Dec 2022 21:30:35 +0100 Subject: [PATCH 06/85] Add style attr; Aligned names to scala-dom-types --- calico/src/main/scala/calico/html.scala | 60 ++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 8268a56b..3cb0210c 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -186,7 +186,8 @@ trait HtmlBuilders[F[_]](using F: Async[F]) DataPropModifiers[F], EventPropModifiers[F], ChildrenModifiers[F], - KeyedChildrenModifiers[F]: + KeyedChildrenModifiers[F], + StylePropModifiers[F]: protected def htmlTag[E <: fs2.dom.HtmlElement[F]](tagName: String, void: Boolean) = HtmlTag(tagName, void) @@ -209,13 +210,15 @@ trait HtmlBuilders[F[_]](using F: Async[F]) def cls: ClassProp[F] = ClassProp[F] - def data(suffix: String): DataProp[F] = DataProp[F](suffix) + def dataAttr(suffix: String): DataProp[F] = DataProp[F](suffix) def children: Children[F] = Children[F] def children[K](f: K => Resource[F, fs2.dom.Node[F]]): KeyedChildren[F, K] = KeyedChildren[F, K](f) + def styleAttr: StyleProp[F] = StyleProp[F] + type HtmlTagT[F[_]] = [E <: fs2.dom.HtmlElement[F]] =>> HtmlTag[F, E] final class HtmlTag[F[_], E <: fs2.dom.HtmlElement[F]] private[calico] ( @@ -518,6 +521,59 @@ trait ClassPropModifiers[F[_]](using F: Async[F]): private val _forConstantClassProp: Modifier[F, Any, SingleConstantModifier] = (m, n) => Resource.eval(F.delay(n.asInstanceOf[js.Dictionary[String]]("className") = m.cls)) +final class StyleProp[F[_]] private[calico]: + import StyleProp.* + + inline def :=(v: String): ConstantModifier = + ConstantModifier(v) + + inline def <--(vs: Signal[F, String]): SignalModifier[F] = + SignalModifier(vs) + + inline def <--(vs: Signal[F, Option[String]]): OptionSignalModifier[F] = + OptionSignalModifier(vs) + +object StyleProp: + final class ConstantModifier( + val value: String + ) + + final class SignalModifier[F[_]]( + val values: Signal[F, String] + ) + + final class OptionSignalModifier[F[_]]( + val values: Signal[F, Option[String]] + ) + +trait StylePropModifiers[F[_]](using F: Async[F]): + import StyleProp.* + + private inline def setStyleProp[N](node: N, value: String) = + F.delay(node.asInstanceOf[dom.HTMLElement].style = value) + + inline given forConstantStyleProp[N <: fs2.dom.HtmlElement[F]] + : Modifier[F, N, ConstantModifier] = + _forConstantStyleProp.asInstanceOf[Modifier[F, N, ConstantModifier]] + + private val _forConstantStyleProp: Modifier[F, fs2.dom.HtmlElement[F], ConstantModifier] = + (m, n) => Resource.eval(setStyleProp(n, m.value)) + + private val _forSignalStyleProp: Modifier[F, Any, SignalModifier[F]] = + Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => + setStyleProp(any, s))(_.values) + + inline given forOptionSignalStyleProp[N]: Modifier[F, N, OptionSignalModifier[F]] = + _forOptionSignalStyleProp.asInstanceOf[Modifier[F, N, OptionSignalModifier[F]]] + + private val _forOptionSignalStyleProp: Modifier[F, Any, OptionSignalModifier[F]] = + Modifier.forSignal[F, Any, OptionSignalModifier[F], Option[String]]((any, osm, os) => + F.delay { + val e = any.asInstanceOf[dom.HTMLElement] + os.fold(e.removeAttribute("style"))(e.style = _) + () + })(_.values) + final class DataProp[F[_]] private[calico] (name: String): import DataProp.* From b286352f8d630e848e0f7b914416a86b15b142ca Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Sat, 10 Dec 2022 14:03:04 +0100 Subject: [PATCH 07/85] Add role attr --- calico/src/main/scala/calico/html.scala | 89 ++++++++++++++++++++----- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 3cb0210c..b2713396 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -50,6 +50,7 @@ import org.scalajs.dom import shapeless3.deriving.K0 import com.raquo.domtypes.generic.defs.attrs.AriaAttrs +import org.scalajs.dom.Attr import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.scalajs.js @@ -187,7 +188,8 @@ trait HtmlBuilders[F[_]](using F: Async[F]) EventPropModifiers[F], ChildrenModifiers[F], KeyedChildrenModifiers[F], - StylePropModifiers[F]: + StylePropModifiers[F], + RolePropModifiers[F]: protected def htmlTag[E <: fs2.dom.HtmlElement[F]](tagName: String, void: Boolean) = HtmlTag(tagName, void) @@ -210,6 +212,8 @@ trait HtmlBuilders[F[_]](using F: Async[F]) def cls: ClassProp[F] = ClassProp[F] + def role: RoleProp[F] = RoleProp[F] + def dataAttr(suffix: String): DataProp[F] = DataProp[F](suffix) def children: Children[F] = Children[F] @@ -493,18 +497,7 @@ trait EventPropModifiers[F[_]](using F: Async[F]): final class ClassProp[F[_]] private[calico] extends Prop[F, List[String], String]( "className", - new: - def decode(domValue: String) = domValue.split(" ").toList - - def encode(scalaValue: List[String]) = - if scalaValue.isEmpty then "" - else - var acc = scalaValue.head - var tail = scalaValue.tail - while tail.nonEmpty do - acc += " " + tail.head - tail = tail.tail - acc + whitespaceSeparatedStringsCodec ): import ClassProp.* @@ -560,8 +553,8 @@ trait StylePropModifiers[F[_]](using F: Async[F]): (m, n) => Resource.eval(setStyleProp(n, m.value)) private val _forSignalStyleProp: Modifier[F, Any, SignalModifier[F]] = - Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => - setStyleProp(any, s))(_.values) + Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => setStyleProp(any, s))( + _.values) inline given forOptionSignalStyleProp[N]: Modifier[F, N, OptionSignalModifier[F]] = _forOptionSignalStyleProp.asInstanceOf[Modifier[F, N, OptionSignalModifier[F]]] @@ -574,6 +567,59 @@ trait StylePropModifiers[F[_]](using F: Async[F]): () })(_.values) +final class RoleProp[F[_]] private[calico]: + import RoleProp.* + + inline def :=(v: String): ConstantModifier = + ConstantModifier(v) + + inline def <--(vs: Signal[F, String]): SignalModifier[F] = + SignalModifier(vs) + + inline def <--(vs: Signal[F, Option[String]]): OptionSignalModifier[F] = + OptionSignalModifier(vs) + +object RoleProp: + final class ConstantModifier( + val value: String + ) + + final class SignalModifier[F[_]]( + val values: Signal[F, String] + ) + + final class OptionSignalModifier[F[_]]( + val values: Signal[F, Option[String]] + ) + +trait RolePropModifiers[F[_]](using F: Async[F]): + import RoleProp.* + + private inline def setRoleProp[N](node: N, value: String) = + F.delay(node.asInstanceOf[dom.Element].setAttribute("role", value)) + + inline given forConstantRoleProp[N <: fs2.dom.HtmlElement[F]] + : Modifier[F, N, ConstantModifier] = + _forConstantRoleProp.asInstanceOf[Modifier[F, N, ConstantModifier]] + + private val _forConstantRoleProp: Modifier[F, fs2.dom.HtmlElement[F], ConstantModifier] = + (m, n) => Resource.eval(setRoleProp(n, m.value)) + + private val _forSignalRoleProp: Modifier[F, Any, SignalModifier[F]] = + Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => setRoleProp(any, s))( + _.values) + + inline given forOptionSignalRoleProp[N]: Modifier[F, N, OptionSignalModifier[F]] = + _forOptionSignalRoleProp.asInstanceOf[Modifier[F, N, OptionSignalModifier[F]]] + + private val _forOptionSignalRoleProp: Modifier[F, Any, OptionSignalModifier[F]] = + Modifier.forSignal[F, Any, OptionSignalModifier[F], Option[String]]((any, osm, os) => + F.delay { + val e = any.asInstanceOf[dom.Element] + os.fold(e.removeAttribute("role"))(e.setAttribute("role", _)) + () + })(_.values) + final class DataProp[F[_]] private[calico] (name: String): import DataProp.* @@ -782,3 +828,16 @@ trait KeyedChildrenModifiers[F[_]](using F: Async[F]): .drain .cedeBackground yield () + +private val whitespaceSeparatedStringsCodec: Codec[List[String], String] = new: + def decode(domValue: String) = domValue.split(" ").toList + + def encode(scalaValue: List[String]) = + if scalaValue.isEmpty then "" + else + var acc = scalaValue.head + var tail = scalaValue.tail + while tail.nonEmpty do + acc += " " + tail.head + tail = tail.tail + acc From 8dd5d99e36286e84526d7022216abddce01eb6f1 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Sat, 10 Dec 2022 14:16:00 +0100 Subject: [PATCH 08/85] Add aria.current --- calico/src/main/scala/calico/html.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index b2713396..3585ab08 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -172,7 +172,9 @@ trait Html[F[_]] object Aria: def apply[F[_]: Async]: Aria[F] = new Aria[F] {} -trait Aria[F[_]] extends HtmlBuilders[F], AriaAttrs[HtmlAttr[F, _]] +trait Aria[F[_]] extends HtmlBuilders[F], AriaAttrs[HtmlAttr[F, _]]: + //TODO remove this once there's a new release of scala-dom-types + lazy val current: HtmlAttr[F, String] = stringHtmlAttr("aria-current") trait HtmlBuilders[F[_]](using F: Async[F]) extends HtmlTagBuilder[HtmlTagT[F], fs2.dom.HtmlElement[F]], From 6c72195748f499ee35b10d1abaa104efba9a68d3 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Sat, 10 Dec 2022 14:22:34 +0100 Subject: [PATCH 09/85] Reuse HtmlAttr --- calico/src/main/scala/calico/html.scala | 58 +------------------------ 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 3585ab08..b9c5a1c6 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -190,8 +190,7 @@ trait HtmlBuilders[F[_]](using F: Async[F]) EventPropModifiers[F], ChildrenModifiers[F], KeyedChildrenModifiers[F], - StylePropModifiers[F], - RolePropModifiers[F]: + StylePropModifiers[F]: protected def htmlTag[E <: fs2.dom.HtmlElement[F]](tagName: String, void: Boolean) = HtmlTag(tagName, void) @@ -214,7 +213,7 @@ trait HtmlBuilders[F[_]](using F: Async[F]) def cls: ClassProp[F] = ClassProp[F] - def role: RoleProp[F] = RoleProp[F] + def role: HtmlAttr[F, List[String]] = HtmlAttr("role", whitespaceSeparatedStringsCodec) def dataAttr(suffix: String): DataProp[F] = DataProp[F](suffix) @@ -569,59 +568,6 @@ trait StylePropModifiers[F[_]](using F: Async[F]): () })(_.values) -final class RoleProp[F[_]] private[calico]: - import RoleProp.* - - inline def :=(v: String): ConstantModifier = - ConstantModifier(v) - - inline def <--(vs: Signal[F, String]): SignalModifier[F] = - SignalModifier(vs) - - inline def <--(vs: Signal[F, Option[String]]): OptionSignalModifier[F] = - OptionSignalModifier(vs) - -object RoleProp: - final class ConstantModifier( - val value: String - ) - - final class SignalModifier[F[_]]( - val values: Signal[F, String] - ) - - final class OptionSignalModifier[F[_]]( - val values: Signal[F, Option[String]] - ) - -trait RolePropModifiers[F[_]](using F: Async[F]): - import RoleProp.* - - private inline def setRoleProp[N](node: N, value: String) = - F.delay(node.asInstanceOf[dom.Element].setAttribute("role", value)) - - inline given forConstantRoleProp[N <: fs2.dom.HtmlElement[F]] - : Modifier[F, N, ConstantModifier] = - _forConstantRoleProp.asInstanceOf[Modifier[F, N, ConstantModifier]] - - private val _forConstantRoleProp: Modifier[F, fs2.dom.HtmlElement[F], ConstantModifier] = - (m, n) => Resource.eval(setRoleProp(n, m.value)) - - private val _forSignalRoleProp: Modifier[F, Any, SignalModifier[F]] = - Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => setRoleProp(any, s))( - _.values) - - inline given forOptionSignalRoleProp[N]: Modifier[F, N, OptionSignalModifier[F]] = - _forOptionSignalRoleProp.asInstanceOf[Modifier[F, N, OptionSignalModifier[F]]] - - private val _forOptionSignalRoleProp: Modifier[F, Any, OptionSignalModifier[F]] = - Modifier.forSignal[F, Any, OptionSignalModifier[F], Option[String]]((any, osm, os) => - F.delay { - val e = any.asInstanceOf[dom.Element] - os.fold(e.removeAttribute("role"))(e.setAttribute("role", _)) - () - })(_.values) - final class DataProp[F[_]] private[calico] (name: String): import DataProp.* From 564b2aba0422798f94e9c93973859f88c347473e Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Tue, 20 Dec 2022 09:28:03 +0100 Subject: [PATCH 10/85] Add forgotten Modifier instance --- calico/src/main/scala/calico/html.scala | 3 +++ todo-mvc/src/main/scala/todomvc/TodoMvc.scala | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index b9c5a1c6..17abaaa1 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -609,6 +609,9 @@ trait DataPropModifiers[F[_]](using F: Async[F]): private val _forConstantDataProp: Modifier[F, fs2.dom.HtmlElement[F], ConstantModifier] = (m, n) => Resource.eval(setDataProp(n, m.value, m.name)) + inline given forSignalDataProp[N]: Modifier[F, N, SignalModifier[F]] = + _forSignalDataProp.asInstanceOf[Modifier[F, N, SignalModifier[F]]] + private val _forSignalDataProp: Modifier[F, Any, SignalModifier[F]] = Modifier.forSignal[F, Any, SignalModifier[F], String]((any, sm, s) => setDataProp(any, s, sm.name))(_.values) diff --git a/todo-mvc/src/main/scala/todomvc/TodoMvc.scala b/todo-mvc/src/main/scala/todomvc/TodoMvc.scala index 647d5cc6..60d1db4e 100644 --- a/todo-mvc/src/main/scala/todomvc/TodoMvc.scala +++ b/todo-mvc/src/main/scala/todomvc/TodoMvc.scala @@ -47,7 +47,9 @@ object TodoMvc extends IOWebApp: case _ => Filter.All } { filter => div( - cls := "todoapp", + role := List("testrole1"), + cls := List("todoapp"), + aria.current := "page", div(cls := "header", h1("todos"), TodoInput(store)), div( cls := "main", From ad758c247e438fb38f529f2c8a0a521246f7aa66 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Tue, 20 Dec 2022 12:58:43 +0100 Subject: [PATCH 11/85] Workaround potential SignallingRef issue --- calico/src/main/scala/calico/syntax.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/calico/src/main/scala/calico/syntax.scala b/calico/src/main/scala/calico/syntax.scala index 6574fc66..0e62227a 100644 --- a/calico/src/main/scala/calico/syntax.scala +++ b/calico/src/main/scala/calico/syntax.scala @@ -17,7 +17,6 @@ package calico package syntax -import cats.Functor import cats.data.State import cats.effect.kernel.Async import cats.effect.kernel.Concurrent @@ -28,16 +27,17 @@ import cats.effect.kernel.Ref import cats.effect.kernel.Resource import cats.effect.kernel.Sync import cats.effect.syntax.all.* +import cats.Functor import cats.kernel.Eq import cats.syntax.all.* -import fs2.Pipe -import fs2.Pull -import fs2.Stream import fs2.concurrent.Channel import fs2.concurrent.Signal import fs2.concurrent.SignallingRef import fs2.concurrent.Topic import fs2.dom.Dom +import fs2.Pipe +import fs2.Pull +import fs2.Stream import monocle.Lens import org.scalajs.dom @@ -68,7 +68,7 @@ extension [F[_], A](fa: F[A]) extension [F[_], A](signal: Signal[F, A]) private[calico] def getAndUpdates(using Concurrent[F]): Resource[F, (A, Stream[F, A])] = // this hack makes me sad - Resource.eval(signal.get.tupleRight(signal.discrete.drop(1))) + Resource.eval(signal.get.tupleRight(signal.discrete)) def changes(using Eq[A]): Signal[F, A] = new: From 302d7af2f62433e20ce950bddd7fc09d7ab59760 Mon Sep 17 00:00:00 2001 From: Matthias Herrmann Date: Tue, 3 Jan 2023 13:39:54 +0100 Subject: [PATCH 12/85] Started codegeneration POC --- build.sbt | 8 +- calico/src/main/scala/calico/html.scala | 106 +- .../src/main/scala/calico/html/Modifier.scala | 108 + .../src/main/scala/calico/html/codecs.scala | 105 + .../calico/html/defs/attrs/AriaAttrs.scala | 374 +++ .../calico/html/defs/attrs/HtmlAttrs.scala | 156 ++ .../calico/html/defs/attrs/SvgAttrs.scala | 1018 +++++++++ .../defs/eventProps/DocumentEventProps.scala | 57 + .../defs/eventProps/GlobalEventProps.scala | 755 ++++++ .../defs/eventProps/WindowEventProps.scala | 134 ++ .../calico/html/defs/props/HtmlProps.scala | 762 ++++++ .../calico/html/defs/styles/StyleProps.scala | 2034 +++++++++++++++++ .../defs/styles/traits/AlignContent.scala | 19 + .../calico/html/defs/styles/traits/Auto.scala | 15 + .../styles/traits/BackfaceVisibility.scala | 19 + .../styles/traits/BackgroundAttachment.scala | 35 + .../defs/styles/traits/BackgroundSize.scala | 30 + .../defs/styles/traits/BorderCollapse.scala | 19 + .../html/defs/styles/traits/BoxSizing.scala | 17 + .../html/defs/styles/traits/Clear.scala | 22 + .../html/defs/styles/traits/Color.scala | 33 + .../html/defs/styles/traits/Cursor.scala | 124 + .../html/defs/styles/traits/Direction.scala | 19 + .../html/defs/styles/traits/Display.scala | 150 ++ .../html/defs/styles/traits/EmptyCells.scala | 19 + .../defs/styles/traits/FlexDirection.scala | 32 + .../defs/styles/traits/FlexPosition.scala | 35 + .../html/defs/styles/traits/FlexWrap.scala | 34 + .../html/defs/styles/traits/Float.scala | 19 + .../html/defs/styles/traits/FontSize.scala | 41 + .../html/defs/styles/traits/FontStyle.scala | 22 + .../html/defs/styles/traits/FontWeight.scala | 31 + .../defs/styles/traits/GlobalKeywords.scala | 41 + .../defs/styles/traits/JustifyContent.scala | 17 + .../calico/html/defs/styles/traits/Line.scala | 67 + .../html/defs/styles/traits/LineWidth.scala | 24 + .../styles/traits/ListStylePosition.scala | 22 + .../defs/styles/traits/ListStyleType.scala | 73 + .../defs/styles/traits/MinMaxLength.scala | 27 + .../defs/styles/traits/MixBlendMode.scala | 43 + .../calico/html/defs/styles/traits/None.scala | 15 + .../html/defs/styles/traits/Normal.scala | 15 + .../html/defs/styles/traits/Overflow.scala | 30 + .../defs/styles/traits/OverflowWrap.scala | 22 + .../defs/styles/traits/PaddingBoxSizing.scala | 15 + .../html/defs/styles/traits/PageBreak.scala | 25 + .../defs/styles/traits/PointerEvents.scala | 95 + .../html/defs/styles/traits/Position.scala | 45 + .../html/defs/styles/traits/TableLayout.scala | 27 + .../html/defs/styles/traits/TextAlign.scala | 34 + .../defs/styles/traits/TextDecoration.scala | 22 + .../defs/styles/traits/TextOverflow.scala | 30 + .../defs/styles/traits/TextTransform.scala | 25 + .../styles/traits/TextUnderlinePosition.scala | 45 + .../defs/styles/traits/VerticalAlign.scala | 62 + .../html/defs/styles/traits/Visibility.scala | 30 + .../html/defs/styles/traits/WhiteSpace.scala | 44 + .../html/defs/styles/traits/WordBreak.scala | 34 + .../main/scala/calico/html/keys/Attr.scala | 71 + .../main/scala/calico/util/DomHotswap.scala | 3 +- project/DomDefsGenerator.scala | 374 +++ project/build.sbt | 12 + project/lastScalaDomTypesVersion.txt | 1 + 63 files changed, 7640 insertions(+), 102 deletions(-) create mode 100644 calico/src/main/scala/calico/html/Modifier.scala create mode 100644 calico/src/main/scala/calico/html/codecs.scala create mode 100644 calico/src/main/scala/calico/html/defs/attrs/AriaAttrs.scala create mode 100644 calico/src/main/scala/calico/html/defs/attrs/HtmlAttrs.scala create mode 100644 calico/src/main/scala/calico/html/defs/attrs/SvgAttrs.scala create mode 100644 calico/src/main/scala/calico/html/defs/eventProps/DocumentEventProps.scala create mode 100644 calico/src/main/scala/calico/html/defs/eventProps/GlobalEventProps.scala create mode 100644 calico/src/main/scala/calico/html/defs/eventProps/WindowEventProps.scala create mode 100644 calico/src/main/scala/calico/html/defs/props/HtmlProps.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/StyleProps.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/AlignContent.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Auto.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/BackfaceVisibility.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/BackgroundAttachment.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/BackgroundSize.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/BorderCollapse.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/BoxSizing.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Clear.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Color.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Cursor.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Direction.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Display.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/EmptyCells.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/FlexDirection.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/FlexPosition.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/FlexWrap.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Float.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/FontSize.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/FontStyle.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/FontWeight.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/GlobalKeywords.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/JustifyContent.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Line.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/LineWidth.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/ListStylePosition.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/ListStyleType.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/MinMaxLength.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/MixBlendMode.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/None.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Normal.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Overflow.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/OverflowWrap.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/PaddingBoxSizing.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/PageBreak.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/PointerEvents.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Position.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/TableLayout.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/TextAlign.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/TextDecoration.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/TextOverflow.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/TextTransform.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/TextUnderlinePosition.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/VerticalAlign.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/Visibility.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/WhiteSpace.scala create mode 100644 calico/src/main/scala/calico/html/defs/styles/traits/WordBreak.scala create mode 100644 calico/src/main/scala/calico/html/keys/Attr.scala create mode 100644 project/DomDefsGenerator.scala create mode 100644 project/build.sbt create mode 100644 project/lastScalaDomTypesVersion.txt diff --git a/build.sbt b/build.sbt index 511dd9f3..8662efc5 100644 --- a/build.sbt +++ b/build.sbt @@ -11,13 +11,19 @@ ThisBuild / tlSitePublishBranch := Some("series/0.1") ThisBuild / tlSonatypeUseLegacyHost := false ThisBuild / crossScalaVersions := Seq("3.2.1") -ThisBuild / scalacOptions ++= Seq("-new-syntax", "-indent", "-source:future") +ThisBuild / scalacOptions ++= Seq("-new-syntax", "-indent") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) ThisBuild / tlJdkRelease := Some(8) ThisBuild / resolvers ++= Resolver.sonatypeOssRepos("snapshots") +lazy val precompile = taskKey[Unit]("runs Calico-specific pre-compile tasks") + +precompile := DomDefsGenerator.cachedGenerate() + +(Compile / compile) := ((Compile / compile) dependsOn precompile).value + val CatsVersion = "2.8.0" val CatsEffectVersion = "3.4.0-RC2" val Fs2Version = "3.3.0-113-afb5afc-SNAPSHOT" diff --git a/calico/src/main/scala/calico/html.scala b/calico/src/main/scala/calico/html.scala index 17abaaa1..23852c08 100644 --- a/calico/src/main/scala/calico/html.scala +++ b/calico/src/main/scala/calico/html.scala @@ -252,103 +252,6 @@ final class HtmlTag[F[_], E <: fs2.dom.HtmlElement[F]] private[calico] ( private def build = F.delay(dom.document.createElement(name).asInstanceOf[E]) -trait Modifier[F[_], E, A]: - outer => - - def modify(a: A, e: E): Resource[F, Unit] - - inline final def contramap[B](inline f: B => A): Modifier[F, E, B] = - (b: B, e: E) => outer.modify(f(b), e) - -object Modifier: - def forSignal[F[_]: Async, A, B, C](setter: (A, B, C) => F[Unit])( - signal: B => Signal[F, C]): Modifier[F, A, B] = (b, a) => - signal(b).getAndUpdates.flatMap { (head, tail) => - def set(c: C) = setter(a, b, c) - Resource.eval(set(head)) *> - tail.foreach(set(_)).compile.drain.cedeBackground.void - } - -trait Modifiers[F[_]](using F: Async[F]): - inline given forUnit[E]: Modifier[F, E, Unit] = - _forUnit.asInstanceOf[Modifier[F, E, Unit]] - - private val _forUnit: Modifier[F, Any, Unit] = - (_, _) => Resource.unit - - inline given forString[E <: fs2.dom.Node[F]]: Modifier[F, E, String] = - _forString.asInstanceOf[Modifier[F, E, String]] - - private val _forString: Modifier[F, dom.Node, String] = (s, e) => - Resource.eval { - F.delay { - e.appendChild(dom.document.createTextNode(s)) - () - } - } - - inline given forStringSignal[E <: fs2.dom.Node[F]]: Modifier[F, E, Signal[F, String]] = - _forStringSignal.asInstanceOf[Modifier[F, E, Signal[F, String]]] - - private val _forStringSignal: Modifier[F, dom.Node, Signal[F, String]] = (s, e) => - s.getAndUpdates.flatMap { (head, tail) => - Resource - .eval(F.delay(e.appendChild(dom.document.createTextNode(head)))) - .flatMap { n => - tail.foreach(t => F.delay(n.textContent = t)).compile.drain.cedeBackground - } - .void - } - - inline given forStringOptionSignal[E <: fs2.dom.Node[F]] - : Modifier[F, E, Signal[F, Option[String]]] = - _forStringOptionSignal.asInstanceOf[Modifier[F, E, Signal[F, Option[String]]]] - - private val _forStringOptionSignal: Modifier[F, dom.Node, Signal[F, Option[String]]] = - _forStringSignal.contramap(_.map(_.getOrElse(""))) - - given forResource[E <: fs2.dom.Node[F], A]( - using M: Modifier[F, E, A]): Modifier[F, E, Resource[F, A]] = - (a, e) => a.flatMap(M.modify(_, e)) - - given forFoldable[E <: fs2.dom.Node[F], G[_]: Foldable, A]( - using M: Modifier[F, E, A]): Modifier[F, E, G[A]] = - (ga, e) => ga.foldMapM(M.modify(_, e)).void - - inline given forNode[N <: fs2.dom.Node[F], N2 <: fs2.dom.Node[F]] - : Modifier[F, N, Resource[F, N2]] = - _forNode.asInstanceOf[Modifier[F, N, Resource[F, N2]]] - - private val _forNode: Modifier[F, dom.Node, Resource[F, dom.Node]] = (n2, n) => - n2.evalMap(n2 => F.delay(n.appendChild(n2))) - - inline given forNodeSignal[N <: fs2.dom.Node[F], N2 <: fs2.dom.Node[F]] - : Modifier[F, N, Signal[F, Resource[F, N2]]] = - _forNodeSignal.asInstanceOf[Modifier[F, N, Signal[F, Resource[F, N2]]]] - - private val _forNodeSignal: Modifier[F, dom.Node, Signal[F, Resource[F, dom.Node]]] = - (n2s, n) => - n2s.getAndUpdates.flatMap { (head, tail) => - DomHotswap(head).flatMap { (hs, n2) => - F.delay(n.appendChild(n2)).toResource *> - tail - .foreach(hs.swap(_)((n2, n3) => F.delay(n.replaceChild(n3, n2)))) - .compile - .drain - .cedeBackground - }.void - } - - inline given forNodeOptionSignal[N <: fs2.dom.Node[F], N2 <: fs2.dom.Node[F]] - : Modifier[F, N, Signal[F, Option[Resource[F, N2]]]] = - _forNodeOptionSignal.asInstanceOf[Modifier[F, N, Signal[F, Option[Resource[F, N2]]]]] - - private val _forNodeOptionSignal - : Modifier[F, dom.Node, Signal[F, Option[Resource[F, dom.Node]]]] = (n2s, n) => - Resource.eval(F.delay(Resource.pure[F, dom.Node](dom.document.createComment("")))).flatMap { - sentinel => _forNodeSignal.modify(n2s.map(_.getOrElse(sentinel)), n) - } - final class HtmlAttr[F[_], V] private[calico] (key: String, codec: Codec[V, String]): import HtmlAttr.* @@ -681,8 +584,10 @@ trait ChildrenModifiers[F[_]](using F: Async[F]): private def impl(n: dom.Node, children: Signal[F, Resource[F, List[dom.Node]]]) = for - (head, tail) <- children.getAndUpdates - (hs, generation0) <- DomHotswap(head) + tuple <- children.getAndUpdates + (head, tail) = tuple + tuple <- DomHotswap(head) + (hs, generation0) = tuple sentinel <- Resource.eval { F.delay { generation0.foreach(n.appendChild(_)) @@ -724,7 +629,8 @@ trait KeyedChildrenModifiers[F[_]](using F: Async[F]): val n = _n.asInstanceOf[dom.Node] inline def build(k: K) = m.build(k).asInstanceOf[Resource[F, dom.Node]] for - (head, tail) <- m.keys.getAndUpdates + tuple <- m.keys.getAndUpdates + (head, tail) = tuple active <- Resource.makeFull[F, Ref[F, mutable.Map[K, (dom.Node, F[Unit])]]] { poll => def go(keys: List[K], active: mutable.Map[K, (dom.Node, F[Unit])]): F[Unit] = if keys.isEmpty then F.unit diff --git a/calico/src/main/scala/calico/html/Modifier.scala b/calico/src/main/scala/calico/html/Modifier.scala new file mode 100644 index 00000000..2a428c4c --- /dev/null +++ b/calico/src/main/scala/calico/html/Modifier.scala @@ -0,0 +1,108 @@ +package calico.html + +import calico.syntax.* +import calico.util.DomHotswap +import cats.effect.kernel.Async +import cats.effect.kernel.Resource +import cats.effect.syntax.all.* +import cats.Foldable +import cats.syntax.all.* +import fs2.concurrent.Signal +import org.scalajs.dom + +trait Modifier[F[_], E, A]: + outer => + + def modify(a: A, e: E): Resource[F, Unit] + + inline final def contramap[B](inline f: B => A): Modifier[F, E, B] = + (b: B, e: E) => outer.modify(f(b), e) + +object Modifier: + def forSignal[F[_]: Async, A, B, C](setter: (A, B, C) => F[Unit])( + signal: B => Signal[F, C]): Modifier[F, A, B] = (b, a) => + signal(b).getAndUpdates.flatMap { (head, tail) => + def set(c: C) = setter(a, b, c) + Resource.eval(set(head)) *> + tail.foreach(set(_)).compile.drain.cedeBackground.void + } + +trait Modifiers[F[_]](using F: Async[F]): + inline given forUnit[E]: Modifier[F, E, Unit] = + _forUnit.asInstanceOf[Modifier[F, E, Unit]] + + private val _forUnit: Modifier[F, Any, Unit] = + (_, _) => Resource.unit + + inline given forString[E <: fs2.dom.Node[F]]: Modifier[F, E, String] = + _forString.asInstanceOf[Modifier[F, E, String]] + + private val _forString: Modifier[F, dom.Node, String] = (s, e) => + Resource.eval { + F.delay { + e.appendChild(dom.document.createTextNode(s)) + () + } + } + + inline given forStringSignal[E <: fs2.dom.Node[F]]: Modifier[F, E, Signal[F, String]] = + _forStringSignal.asInstanceOf[Modifier[F, E, Signal[F, String]]] + + private val _forStringSignal: Modifier[F, dom.Node, Signal[F, String]] = (s, e) => + s.getAndUpdates.flatMap { (head, tail) => + Resource + .eval(F.delay(e.appendChild(dom.document.createTextNode(head)))) + .flatMap { n => + tail.foreach(t => F.delay(n.textContent = t)).compile.drain.cedeBackground + } + .void + } + + inline given forStringOptionSignal[E <: fs2.dom.Node[F]] + : Modifier[F, E, Signal[F, Option[String]]] = + _forStringOptionSignal.asInstanceOf[Modifier[F, E, Signal[F, Option[String]]]] + + private val _forStringOptionSignal: Modifier[F, dom.Node, Signal[F, Option[String]]] = + _forStringSignal.contramap(_.map(_.getOrElse(""))) + + given forResource[E <: fs2.dom.Node[F], A]( + using M: Modifier[F, E, A]): Modifier[F, E, Resource[F, A]] = + (a, e) => a.flatMap(M.modify(_, e)) + + given forFoldable[E <: fs2.dom.Node[F], G[_]: Foldable, A]( + using M: Modifier[F, E, A]): Modifier[F, E, G[A]] = + (ga, e) => ga.foldMapM(M.modify(_, e)).void + + inline given forNode[N <: fs2.dom.Node[F], N2 <: fs2.dom.Node[F]] + : Modifier[F, N, Resource[F, N2]] = + _forNode.asInstanceOf[Modifier[F, N, Resource[F, N2]]] + + private val _forNode: Modifier[F, dom.Node, Resource[F, dom.Node]] = (n2, n) => + n2.evalMap(n2 => F.delay(n.appendChild(n2))) + + inline given forNodeSignal[N <: fs2.dom.Node[F], N2 <: fs2.dom.Node[F]] + : Modifier[F, N, Signal[F, Resource[F, N2]]] = + _forNodeSignal.asInstanceOf[Modifier[F, N, Signal[F, Resource[F, N2]]]] + + private val _forNodeSignal: Modifier[F, dom.Node, Signal[F, Resource[F, dom.Node]]] = + (n2s, n) => + n2s.getAndUpdates.flatMap { (head, tail) => + DomHotswap(head).flatMap { (hs, n2) => + F.delay(n.appendChild(n2)).toResource *> + tail + .foreach(hs.swap(_)((n2, n3) => F.delay(n.replaceChild(n3, n2)))) + .compile + .drain + .cedeBackground + }.void + } + + inline given forNodeOptionSignal[N <: fs2.dom.Node[F], N2 <: fs2.dom.Node[F]] + : Modifier[F, N, Signal[F, Option[Resource[F, N2]]]] = + _forNodeOptionSignal.asInstanceOf[Modifier[F, N, Signal[F, Option[Resource[F, N2]]]]] + + private val _forNodeOptionSignal + : Modifier[F, dom.Node, Signal[F, Option[Resource[F, dom.Node]]]] = (n2s, n) => + Resource.eval(F.delay(Resource.pure[F, dom.Node](dom.document.createComment("")))).flatMap { + sentinel => _forNodeSignal.modify(n2s.map(_.getOrElse(sentinel)), n) + } diff --git a/calico/src/main/scala/calico/html/codecs.scala b/calico/src/main/scala/calico/html/codecs.scala new file mode 100644 index 00000000..63343dd8 --- /dev/null +++ b/calico/src/main/scala/calico/html/codecs.scala @@ -0,0 +1,105 @@ +package calico.html + +object codecs: + // String Codecs + + object StringAsIsCodec extends AsIsCodec[String] + + // Int Codecs + + object IntAsIsCodec extends AsIsCodec[Int] + + object IntAsStringCodec extends Codec[Int, String] { + override def decode(domValue: String): Int = domValue.toInt // @TODO this can throw exception. How do we handle this? + override def encode(scalaValue: Int): String = scalaValue.toString + } + + // Double Codecs + + object DoubleAsIsCodec extends AsIsCodec[Double] + + object DoubleAsStringCodec extends Codec[Double, String] { + override def decode(domValue: String): Double = domValue.toDouble// @TODO this can throw exception. How do we handle this? + override def encode(scalaValue: Double): String = scalaValue.toString + } + + // Boolean Codecs + + object BooleanAsIsCodec extends AsIsCodec[Boolean] + + object BooleanAsAttrPresenceCodec extends Codec[Boolean, String] { + override def decode(domValue: String): Boolean = domValue != null + override def encode(scalaValue: Boolean): String = if scalaValue then "" else null + } + + object BooleanAsTrueFalseStringCodec extends Codec[Boolean, String] { + override def decode(domValue: String): Boolean = domValue == "true" + override def encode(scalaValue: Boolean): String = if scalaValue then "true" else "false" + } + + object BooleanAsYesNoStringCodec extends Codec[Boolean, String] { + override def decode(domValue: String): Boolean = domValue == "yes" + override def encode(scalaValue: Boolean): String = if scalaValue then "yes" else "no" + } + + object BooleanAsOnOffStringCodec extends Codec[Boolean, String] { + override def decode(domValue: String): Boolean = domValue == "on" + override def encode(scalaValue: Boolean): String = if scalaValue then "on" else "off" + } + + // Iterable Codecs + + object IterableAsSpaceSeparatedStringCodec extends Codec[Iterable[String], String] { // use for e.g. className + override def decode(domValue: String): Iterable[String] = if domValue == "" then Nil else domValue.split(' ') + override def encode(scalaValue: Iterable[String]): String = scalaValue.mkString(" ") + } + + object IterableAsCommaSeparatedStringCodec extends Codec[Iterable[String], String] { // use for lists of IDs + override def decode(domValue: String): Iterable[String] = if domValue == "" then Nil else domValue.split(',') + override def encode(scalaValue: Iterable[String]): String = scalaValue.mkString(",") + } + + /** Use this codec when you don't need any data transformation */ + + trait AsIsCodec[T] extends Codec[T, T] { + override def decode(domValue: T): T = domValue + override def encode(scalaValue: T): T = scalaValue + } + + object AsIsCodec { + + /** Note: We already have several AsIsCodec instances in codecs/package.scala */ + def apply[T]: AsIsCodec[T] = new AsIsCodec[T] {} + } + + /** This trait represents a way to encode and decode HTML attribute or DOM property values. + * + * It is needed because attributes encode all values as strings regardless of their type, + * and then there are also multiple ways to encode e.g. boolean values. Some attributes + * encode those as "true" / "false" strings, others as presence or absence of the element, + * and yet others use "yes" / "no" or "on" / "off" strings, and properties encode booleans + * as actual booleans. + * + * Scala DOM Types hides all this mess from you using codecs. All those pseudo-boolean + * attributes would be simply `Attr[Boolean](name, codec)` in your code. + * */ + trait Codec[ScalaType, DomType] { + + /** Convert the result of a `dom.Node.getAttribute` call to appropriate Scala type. + * + * Note: HTML Attributes are generally optional, and `dom.Node.getAttribute` will return + * `null` if an attribute is not defined on a given DOM node. However, this decoder is + * only intended for cases when the attribute is defined. + */ + def decode(domValue: DomType): ScalaType + + /** Convert desired attribute value to appropriate DOM type. The resulting value should + * be passed to `dom.Node.setAttribute` call, EXCEPT when resulting value is a `null`. + * In that case you should call `dom.Node.removeAttribute` instead. + * + * We use `null` instead of [[Option]] here to reduce overhead in JS land. This method + * should not be called by end users anyway, it's the consuming library's job to + * call this method under the hood. + */ + def encode(scalaValue: ScalaType): DomType + } diff --git a/calico/src/main/scala/calico/html/defs/attrs/AriaAttrs.scala b/calico/src/main/scala/calico/html/defs/attrs/AriaAttrs.scala new file mode 100644 index 00000000..a255ac22 --- /dev/null +++ b/calico/src/main/scala/calico/html/defs/attrs/AriaAttrs.scala @@ -0,0 +1,374 @@ +package calico.html.defs.attrs + +import calico.html.keys.AriaAttr +import calico.html.codecs._ + +// #NOTE: GENERATED CODE +// - This file is generated at compile time from the data in Scala DOM Types +// - See `project/DomDefsGenerator.scala` for code generation params +// - Contribute to https://github.com/raquo/scala-dom-types to add missing tags / attrs / props / etc. + +trait AriaAttrs { + + + /** + * Create ARIA attribute (Note: for HTML attrs, use L.htmlAttr) + * + * @param key - suffix of the attribute, without "aria-" prefix, e.g. "labelledby" + * @param codec - used to encode V into String, e.g. StringAsIsCodec + * + * @tparam V - value type for this attr in Scala + */ + def ariaAttr[V](key: String, codec: Codec[V, String]): AriaAttr[V] = new AriaAttr(key, codec) + + + @inline protected def boolAsTrueFalseAriaAttr(key: String): AriaAttr[Boolean] = ariaAttr(key, BooleanAsTrueFalseStringCodec) + + @inline protected def doubleAriaAttr(key: String): AriaAttr[Double] = ariaAttr(key, DoubleAsStringCodec) + + @inline protected def intAriaAttr(key: String): AriaAttr[Int] = ariaAttr(key, IntAsStringCodec) + + @inline protected def stringAriaAttr(key: String): AriaAttr[String] = ariaAttr(key, StringAsIsCodec) + + + + /** + * Identifies the currently active descendant of a composite widget. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-activedescendant + */ + lazy val activeDescendant: AriaAttr[String] = stringAriaAttr("activedescendant") + + + /** + * Indicates whether assistive technologies will present all, or only parts of, the + * changed region based on the change notifications defined by the aria-relevant + * attribute. See related [[relevant]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-atomic + */ + lazy val atomic: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("atomic") + + + /** + * Indicates whether user input completion suggestions are provided. + * + * Enumerated: "inline" | "list" | "both" | "none" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-autocomplete + */ + lazy val autoComplete: AriaAttr[String] = stringAriaAttr("autocomplete") + + + /** + * Indicates whether an element, and its subtree, are currently being updated. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-busy + */ + lazy val busy: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("busy") + + + /** + * Indicates the current "checked" state of checkboxes, radio buttons, and other + * widgets. See related [[pressed]] and [[selected]]. + * + * Enumerated: Tristate – "true" | "false" | "mixed" | undefined (default) + * - undefined means the element does not support being checked + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-checked + */ + lazy val checked: AriaAttr[String] = stringAriaAttr("checked") + + + /** + * Identifies the element (or elements) whose contents or presence are controlled + * by the current element. See related [[owns]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls + */ + lazy val controls: AriaAttr[String] = stringAriaAttr("controls") + + + /** + * Indicates the element that represents the current item within a container + * or set of related elements. + * + * Enumerated: + * "page" | "step" | "location" | "date" | "time" | "true" | "false" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current + */ + lazy val current: AriaAttr[String] = stringAriaAttr("current") + + + /** + * Identifies the element (or elements) that describes the object. + * See related [[labelledBy]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby + */ + lazy val describedBy: AriaAttr[String] = stringAriaAttr("describedby") + + + /** + * Indicates that the element is perceivable but disabled, so it is not editable + * or otherwise operable. See related [[hidden]] and [[readOnly]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled + */ + lazy val disabled: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("disabled") + + + /** + * Indicates what functions can be performed when the dragged object is released + * on the drop target. This allows assistive technologies to convey the possible + * drag options available to users, including whether a pop-up menu of choices is + * provided by the application. Typically, drop effect functions can only be + * provided once an object has been grabbed for a drag operation as the drop + * effect functions available are dependent on the object being dragged. + * + * Enumerated: "copy" | "move" | "link" | "execute" | "popup" | "none" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-dropeffect + */ + lazy val dropEffect: AriaAttr[String] = stringAriaAttr("dropeffect") + + + /** + * Indicates whether the element, or another grouping element it controls, is + * currently expanded or collapsed. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded + */ + lazy val expanded: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("expanded") + + + /** + * Identifies the next element (or elements) in an alternate reading order of + * content which, at the user's discretion, allows assistive technology to + * override the general default of reading in document source order. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-flowto + */ + lazy val flowTo: AriaAttr[String] = stringAriaAttr("flowto") + + + /** + * Indicates an element's "grabbed" state in a drag-and-drop operation. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-grabbed + */ + lazy val grabbed: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("grabbed") + + + /** + * Indicates that the element has a popup context menu or sub-level menu. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-haspopup + */ + lazy val hasPopup: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("haspopup") + + + /** + * Indicates that the element and all of its descendants are not visible or + * perceivable to any user as implemented by the author. + * See related [[disabled]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden + */ + lazy val hidden: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("hidden") + + + /** + * Indicates the entered value does not conform to the format expected by the + * application. + * + * Enumerated: "grammar" | "spelling" | "true" | "false" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid + */ + lazy val invalid: AriaAttr[String] = stringAriaAttr("invalid") + + + /** + * Defines a string value that labels the current element. + * See related [[labelledBy]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label + */ + lazy val label: AriaAttr[String] = stringAriaAttr("label") + + + /** + * Identifies the element (or elements) that labels the current element. + * See related [[label]] and [[describedBy]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby + */ + lazy val labelledBy: AriaAttr[String] = stringAriaAttr("labelledby") + + + /** + * Defines the hierarchical level of an element within a structure. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-level + */ + lazy val level: AriaAttr[Int] = intAriaAttr("level") + + + /** + * Indicates that an element will be updated, and describes the types of updates the + * user agents, assistive technologies, and user can expect from the live region. + * + * Enumerated: "polite" | "assertive" | "off" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live + */ + lazy val live: AriaAttr[String] = stringAriaAttr("live") + + + /** + * Indicates whether a text box accepts multiple lines of input or only a single line. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-multiline + */ + lazy val multiLine: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("multiline") + + + /** + * Indicates that the user may select more than one item from the current selectable descendants. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-multiselectable + */ + lazy val multiSelectable: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("multiselectable") + + + /** + * Indicates whether the element and orientation is horizontal or vertical. + * + * Enumerated: "vertical" | "horizontal" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-orientation + */ + lazy val orientation: AriaAttr[String] = stringAriaAttr("orientation") + + + /** + * Identifies an element (or elements) in order to define a visual, functional, or + * contextual parent/child relationship between DOM elements where the DOM hierarchy + * cannot be used to represent the relationship. See related [[controls]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-owns + */ + lazy val owns: AriaAttr[String] = stringAriaAttr("owns") + + + /** + * Defines an element's number or position in the current set of listitems or treeitems. + * Not required if all elements in the set are present in the DOM. See related [[setSize]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-posinset + */ + lazy val posInSet: AriaAttr[Int] = intAriaAttr("posinset") + + + /** + * Indicates the current "pressed" state of toggle buttons. See related [[checked]] and [[selected]]. + * + * Enumerated: Tristate – "true" | "false" | "mixed" | undefined (default) + * - undefined means the element does not support being pressed + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed + */ + lazy val pressed: AriaAttr[String] = stringAriaAttr("pressed") + + + /** + * Indicates that the element is not editable, but is otherwise operable. See related [[disabled]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-readonly + */ + lazy val readOnly: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("readonly") + + + /** + * Indicates what user agent change notifications (additions, removals, etc.) + * assistive technologies will receive within a live region. See related [[atomic]]. + * + * Enumerated: "additions" | "removals" | "text" | "all" | "additions text" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-relevant + */ + lazy val relevant: AriaAttr[String] = stringAriaAttr("relevant") + + + /** + * Indicates that user input is required on the element before a form may be submitted. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-required + */ + lazy val required: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("required") + + + /** + * Indicates the current "selected" state of various widgets. + * See related [[checked]] and [[pressed]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-selected + */ + lazy val selected: AriaAttr[Boolean] = boolAsTrueFalseAriaAttr("selected") + + + /** + * Defines the number of items in the current set of listitems or treeitems. + * Not required if all elements in the set are present in the DOM. + * See related [[posInSet]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-setsize + */ + lazy val setSize: AriaAttr[Int] = intAriaAttr("setsize") + + + /** + * Indicates if items in a table or grid are sorted in ascending or descending order. + * + * Enumerated: "ascending" | "descending" | "other" | "none" (default) + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-sort + */ + lazy val sort: AriaAttr[String] = stringAriaAttr("sort") + + + /** + * Defines the maximum allowed value for a range widget. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuemax + */ + lazy val valueMax: AriaAttr[Double] = doubleAriaAttr("valuemax") + + + /** + * Defines the minimum allowed value for a range widget. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuemin + */ + lazy val valueMin: AriaAttr[Double] = doubleAriaAttr("valuemin") + + + /** + * Defines the current value for a range widget. See related [[valueText]]. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow + */ + lazy val valueNow: AriaAttr[Double] = doubleAriaAttr("valuenow") + + + /** + * Defines the human readable text alternative of aria-valuenow for a range widget. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuetext + */ + lazy val valueText: AriaAttr[String] = stringAriaAttr("valuetext") + + +} diff --git a/calico/src/main/scala/calico/html/defs/attrs/HtmlAttrs.scala b/calico/src/main/scala/calico/html/defs/attrs/HtmlAttrs.scala new file mode 100644 index 00000000..c700c610 --- /dev/null +++ b/calico/src/main/scala/calico/html/defs/attrs/HtmlAttrs.scala @@ -0,0 +1,156 @@ +package calico.html.defs.attrs + +import calico.html.keys.HtmlAttr +import calico.html.codecs._ + +// #NOTE: GENERATED CODE +// - This file is generated at compile time from the data in Scala DOM Types +// - See `project/DomDefsGenerator.scala` for code generation params +// - Contribute to https://github.com/raquo/scala-dom-types to add missing tags / attrs / props / etc. + +trait HtmlAttrs { + + + /** + * Create HTML attribute (Note: for SVG attrs, use L.svg.svgAttr) + * + * @param key - name of the attribute, e.g. "value" + * @param codec - used to encode V into String, e.g. StringAsIsCodec + * + * @tparam V - value type for this attr in Scala + */ + def htmlAttr[V](key: String, codec: Codec[V, String]): HtmlAttr[V] = new HtmlAttr(key, codec) + + + @inline protected def boolAsOnOffHtmlAttr(key: String): HtmlAttr[Boolean] = htmlAttr(key, BooleanAsOnOffStringCodec) + + @inline protected def boolAsTrueFalseHtmlAttr(key: String): HtmlAttr[Boolean] = htmlAttr(key, BooleanAsTrueFalseStringCodec) + + @inline protected def intHtmlAttr(key: String): HtmlAttr[Int] = htmlAttr(key, IntAsStringCodec) + + @inline protected def stringHtmlAttr(key: String): HtmlAttr[String] = htmlAttr(key, StringAsIsCodec) + + + + /** + * Declares the character encoding of the page or script. Used on meta and + * script elements. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#attr-charset + */ + lazy val charset: HtmlAttr[String] = stringHtmlAttr("charset") + + + /** + * Indicates whether the element should be editable by the user. + * If so, the browser modifies its widget to allow editing. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/contentEditable + */ + lazy val contentEditable: HtmlAttr[Boolean] = boolAsTrueFalseHtmlAttr("contenteditable") + + + /** + * Specifies a context menu for an element by its element id. + * The context menu appears when a user right-clicks on the element + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contextmenu + */ + lazy val contextMenuId: HtmlAttr[String] = stringHtmlAttr("contextmenu") + + + /** + * Specifies whether the dragged data is copied, moved, or linked, when dropped + * Acceptable values: `copy` | `move` | `link` + */ + lazy val dropZone: HtmlAttr[String] = stringHtmlAttr("dropzone") + + + /** The form attribute specifies an ID of the form an `` element belongs to. */ + lazy val formId: HtmlAttr[String] = stringHtmlAttr("form") + + + /** + * The `height` attribute specifies the pixel height of the following elements: + * `, ,