Add all phx html attributes

This commit is contained in:
Paul-Henri Froidmont 2025-09-18 21:32:02 +02:00
parent 42f1729745
commit 763788fb89
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
6 changed files with 171 additions and 47 deletions

View file

@ -78,6 +78,70 @@ class DomDefsGenerator(baseOutputDirectoryPath: String):
format = format format = format
).printTrait().getOutput() ).printTrait().getOutput()
end generateTagsTrait end generateTagsTrait
override def generateAttrsTrait(
defGroups: List[(String, List[AttrDef])],
printDefGroupComments: Boolean,
traitCommentLines: List[String],
traitModifiers: List[String],
traitName: String,
keyKind: String,
implNameSuffix: String,
baseImplDefComments: List[String],
baseImplName: String,
namespaceImports: List[String],
namespaceImpl: String => String,
transformAttrDomName: String => String,
defType: DefType
): String =
val (defs, defGroupComments) = defsAndGroupComments(defGroups, printDefGroupComments)
val tagTypes = defs.foldLeft(List[TagType]())((acc, k) => (acc :+ k.tagType).distinct)
if tagTypes.size > 1 then
throw new Exception(
"Sorry, generateAttrsTrait does not support mixing attrs of different types in one call. You can contribute a PR (please contact us first), or bypass this limitation by calling AttrsTraitGenerator manually."
)
val tagType = tagTypes.head
val baseImplDef =
if tagType == SvgTagType then
List(
s"def $baseImplName[V]($keyImplNameArgName: String, encoder: Encoder[V, String], namespace: Option[String]): $keyKind[V] = ${keyKindConstructor(keyKind)}($keyImplNameArgName, encoder, namespace)"
)
else
List(
s"def $baseImplName[V]($keyImplNameArgName: String, encoder: Encoder[V, String]): $keyKind[V] = ${keyKindConstructor(keyKind)}($keyImplNameArgName, encoder)"
)
val headerLines = List(
s"package $attrDefsPackagePath",
"",
keyTypeImport(keyKind),
codecsImport
) ++ namespaceImports ++ List("") ++ standardTraitCommentLines.map("// " + _)
new AttrsTraitGenerator(
defs = defs.map(d => d.copy(domName = transformAttrDomName(d.domName))),
defGroupComments = defGroupComments,
headerLines = headerLines,
traitCommentLines = traitCommentLines,
traitModifiers = traitModifiers,
traitName = traitName,
traitExtends = Nil,
traitThisType = None,
defType = _ => defType,
keyKind = keyKind,
keyImplName = attr => attrImplName(attr.codec, implNameSuffix),
keyImplNameArgName = keyImplNameArgName,
baseImplDefComments = baseImplDefComments,
baseImplName = baseImplName,
baseImplDef = baseImplDef,
transformCodecName = _ + "Encoder",
namespaceImpl = namespaceImpl,
outputImplDefs = true,
format = format
).printTrait().getOutput()
end generateAttrsTrait
end generator end generator
def generate(): Unit = def generate(): Unit =
@ -134,7 +198,7 @@ class DomDefsGenerator(baseOutputDirectoryPath: String):
"Create HTML attribute (Note: for SVG attrs, use L.svg.svgAttr)", "Create HTML attribute (Note: for SVG attrs, use L.svg.svgAttr)",
"", "",
"@param name - name of the attribute, e.g. \"value\"", "@param name - name of the attribute, e.g. \"value\"",
"@param codec - used to encode V into String, e.g. StringAsIsCodec", "@param codec - used to encode V into String, e.g. StringAsIsEncoder",
"", "",
"@tparam V - value type for this attr in Scala" "@tparam V - value type for this attr in Scala"
), ),

View file

@ -2,8 +2,8 @@ package scalive
import scalive.Mod.Attr import scalive.Mod.Attr
import scalive.Mod.Content import scalive.Mod.Content
import scalive.codecs.BooleanAsAttrPresenceCodec import scalive.codecs.BooleanAsAttrPresenceEncoder
import scalive.codecs.Codec import scalive.codecs.Encoder
import zio.json.* import zio.json.*
class HtmlElement(val tag: HtmlTag, val mods: Vector[Mod]): class HtmlElement(val tag: HtmlTag, val mods: Vector[Mod]):
@ -43,8 +43,8 @@ class HtmlTag(val name: String, val void: Boolean = false):
} }
) )
class HtmlAttr[V](val name: String, val codec: Codec[V, String]): class HtmlAttr[V](val name: String, val codec: Encoder[V, String]):
private inline def isBooleanAsAttrPresence = codec == BooleanAsAttrPresenceCodec private inline def isBooleanAsAttrPresence = codec == BooleanAsAttrPresenceEncoder
def :=(value: V): Mod.Attr = def :=(value: V): Mod.Attr =
if isBooleanAsAttrPresence then if isBooleanAsAttrPresence then

View file

@ -1,12 +1,15 @@
import scalive.codecs.BooleanAsAttrPresenceCodec import scalive.codecs.BooleanAsAttrPresenceEncoder
import scalive.codecs.StringAsIsCodec import scalive.codecs.BooleanAsTrueFalseStringEncoder
import scalive.codecs.Encoder
import scalive.codecs.IntAsStringEncoder
import scalive.codecs.StringAsIsEncoder
import scalive.defs.attrs.HtmlAttrs import scalive.defs.attrs.HtmlAttrs
import scalive.defs.complex.ComplexHtmlKeys import scalive.defs.complex.ComplexHtmlKeys
import scalive.defs.tags.HtmlTags import scalive.defs.tags.HtmlTags
package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys: package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
lazy val defer = htmlAttr("defer", codecs.BooleanAsAttrPresenceCodec) lazy val defer = htmlAttr("defer", codecs.BooleanAsAttrPresenceEncoder)
object link: object link:
def navigate(path: String, mods: Mod*): HtmlElement = def navigate(path: String, mods: Mod*): HtmlElement =
@ -14,20 +17,76 @@ package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
object phx: object phx:
private def phxAttr(suffix: String): HtmlAttr[String] = private def phxAttr(suffix: String): HtmlAttr[String] =
new HtmlAttr(s"phx-$suffix", StringAsIsCodec) new HtmlAttr(s"phx-$suffix", StringAsIsEncoder)
private def phxAttrBool(suffix: String): HtmlAttr[Boolean] =
new HtmlAttr(s"phx-$suffix", BooleanAsTrueFalseStringEncoder)
private def phxAttrInt(suffix: String): HtmlAttr[Int] =
new HtmlAttr(s"phx-$suffix", IntAsStringEncoder)
private def phxAttrJson(suffix: String): HtmlAttrJsonValue = private def phxAttrJson(suffix: String): HtmlAttrJsonValue =
new HtmlAttrJsonValue(s"phx-$suffix") new HtmlAttrJsonValue(s"phx-$suffix")
private def dataPhxAttr(suffix: String): HtmlAttr[String] = private def dataPhxAttr(suffix: String): HtmlAttr[String] =
dataAttr(s"phx-$suffix") dataAttr(s"phx-$suffix")
private[scalive] lazy val session = dataPhxAttr("session") private[scalive] lazy val session = dataPhxAttr("session")
private[scalive] lazy val main = htmlAttr("data-phx-main", BooleanAsAttrPresenceCodec) private[scalive] lazy val main = htmlAttr("data-phx-main", BooleanAsAttrPresenceEncoder)
private[scalive] lazy val link = dataPhxAttr("link") private[scalive] lazy val link = dataPhxAttr("link")
private[scalive] lazy val linkState = dataPhxAttr("link-state") private[scalive] lazy val linkState = dataPhxAttr("link-state")
// Click
lazy val click = phxAttrJson("click") lazy val click = phxAttrJson("click")
lazy val clickAway = phxAttrJson("click-away")
// Focus
lazy val blur = phxAttrJson("blur")
lazy val focus = phxAttrJson("focus")
lazy val windowBlur = phxAttrJson("window-blur")
// Keyboard
lazy val keydown = phxAttrJson("keydown")
lazy val keyup = phxAttrJson("keyup")
lazy val windowKeydown = phxAttrJson("window-keydown")
lazy val windowKeyup = phxAttrJson("window-keyup")
lazy val key = phxAttr("key")
// Scroll
lazy val viewportTop = phxAttrJson("viewport-top")
lazy val viewportBottom = phxAttrJson("viewport-bottom")
// Form
lazy val change = phxAttrJson("change")
lazy val submit = phxAttrJson("submit")
lazy val autoRecover = phxAttrJson("auto-recover")
lazy val triggerAction = phxAttrBool("trigger-action")
// Button
lazy val disableWith = phxAttr("disable-with")
// Socket connection lifecycle
lazy val connected = phxAttrJson("connected")
lazy val disconnected = phxAttrJson("disconnected")
// DOM element lifecycle
lazy val mounted = phxAttrJson("mounted")
lazy val remove = phxAttrJson("remove")
lazy val update = new HtmlAttr["update" | "stream" | "ignore"](s"phx-update", Encoder(identity))
// Client hooks
lazy val hook = phxAttr("hook")
// Rate limiting
lazy val debounce = new HtmlAttr["blur" | Int](
s"phx-debounce",
Encoder {
case _: "blur" => "blur"
case value: Int => value.toString
}
)
lazy val throttle = phxAttrInt("throttle")
def value(key: String) = phxAttr(s"value-$key") def value(key: String) = phxAttr(s"value-$key")
lazy val trackStatic = htmlAttr("phx-track-static", BooleanAsAttrPresenceCodec) lazy val trackStatic = htmlAttr("phx-track-static", BooleanAsAttrPresenceEncoder)
end phx
implicit def stringToMod(v: String): Mod = Mod.Content.Text(v) implicit def stringToMod(v: String): Mod = Mod.Content.Text(v)
implicit def htmlElementToMod(el: HtmlElement): Mod = Mod.Content.Tag(el) implicit def htmlElementToMod(el: HtmlElement): Mod = Mod.Content.Tag(el)
implicit def dynStringToMod(d: Dyn[String]): Mod = Mod.Content.DynText(d) implicit def dynStringToMod(d: Dyn[String]): Mod = Mod.Content.DynText(d)
end scalive

View file

@ -1,29 +0,0 @@
package scalive.codecs
class Codec[ScalaType, DomType](val encode: ScalaType => DomType, val decode: DomType => ScalaType)
def AsIsCodec[V](): Codec[V, V] = Codec(identity, identity)
val StringAsIsCodec: Codec[String, String] = AsIsCodec()
val IntAsIsCodec: Codec[Int, Int] = AsIsCodec()
lazy val IntAsStringCodec: Codec[Int, String] = Codec[Int, String](_.toString, _.toInt)
lazy val DoubleAsIsCodec: Codec[Double, Double] = AsIsCodec()
lazy val DoubleAsStringCodec: Codec[Double, String] = Codec[Double, String](_.toString, _.toDouble)
val BooleanAsIsCodec: Codec[Boolean, Boolean] = AsIsCodec()
lazy val BooleanAsAttrPresenceCodec: Codec[Boolean, String] =
Codec[Boolean, String](if _ then "" else null, _ != null)
lazy val BooleanAsTrueFalseStringCodec: Codec[Boolean, String] =
Codec[Boolean, String](if _ then "true" else "false", _ == "true")
lazy val BooleanAsYesNoStringCodec: Codec[Boolean, String] =
Codec[Boolean, String](if _ then "yes" else "no", _ == "yes")
lazy val BooleanAsOnOffStringCodec: Codec[Boolean, String] =
Codec[Boolean, String](if _ then "on" else "off", _ == "on")

View file

@ -0,0 +1,30 @@
package scalive.codecs
class Encoder[ScalaType, DomType](val encode: ScalaType => DomType)
def AsIsEncoder[V](): Encoder[V, V] = Encoder(identity)
val StringAsIsEncoder: Encoder[String, String] = AsIsEncoder()
val IntAsIsEncoder: Encoder[Int, Int] = AsIsEncoder()
lazy val IntAsStringEncoder: Encoder[Int, String] = Encoder[Int, String](_.toString)
lazy val DoubleAsIsEncoder: Encoder[Double, Double] = AsIsEncoder()
lazy val DoubleAsStringEncoder: Encoder[Double, String] =
Encoder[Double, String](_.toString)
val BooleanAsIsEncoder: Encoder[Boolean, Boolean] = AsIsEncoder()
lazy val BooleanAsAttrPresenceEncoder: Encoder[Boolean, String] =
Encoder[Boolean, String](if _ then "" else null)
lazy val BooleanAsTrueFalseStringEncoder: Encoder[Boolean, String] =
Encoder[Boolean, String](if _ then "true" else "false")
lazy val BooleanAsYesNoStringEncoder: Encoder[Boolean, String] =
Encoder[Boolean, String](if _ then "yes" else "no")
lazy val BooleanAsOnOffStringEncoder: Encoder[Boolean, String] =
Encoder[Boolean, String](if _ then "on" else "off")

View file

@ -12,7 +12,7 @@ trait ComplexHtmlKeys:
* select and access specific elements via the class selectors or functions like the DOM method * select and access specific elements via the class selectors or functions like the DOM method
* document.getElementsByClassName * document.getElementsByClassName
*/ */
val className: HtmlAttr[String] = new HtmlAttr("class", StringAsIsCodec) val className: HtmlAttr[String] = new HtmlAttr("class", StringAsIsEncoder)
val cls: HtmlAttr[String] = className val cls: HtmlAttr[String] = className
@ -22,7 +22,7 @@ trait ComplexHtmlKeys:
* stylesheet, and the href attribute is set to the URL of an external style sheet to format the * stylesheet, and the href attribute is set to the URL of an external style sheet to format the
* page. * page.
*/ */
lazy val rel: HtmlAttr[String] = new HtmlAttr("rel", StringAsIsCodec) lazy val rel: HtmlAttr[String] = new HtmlAttr("rel", StringAsIsEncoder)
/** The attribute describes the role(s) the current element plays in the context of the document. /** The attribute describes the role(s) the current element plays in the context of the document.
* This can be used, for example, by applications and assistive technologies to determine the * This can be used, for example, by applications and assistive technologies to determine the
@ -35,7 +35,7 @@ trait ComplexHtmlKeys:
* *
* See: [[http://www.w3.org/TR/role-attribute/#s_role_module_attributes]] * See: [[http://www.w3.org/TR/role-attribute/#s_role_module_attributes]]
*/ */
lazy val role: HtmlAttr[String] = new HtmlAttr("role", StringAsIsCodec) lazy val role: HtmlAttr[String] = new HtmlAttr("role", StringAsIsEncoder)
/** This class of attributes, called custom data attributes, allows proprietary information to be /** This class of attributes, called custom data attributes, allows proprietary information to be
* exchanged between the HTML and its DOM representation that may be used by scripts. All such * exchanged between the HTML and its DOM representation that may be used by scripts. All such
@ -51,13 +51,13 @@ trait ComplexHtmlKeys:
* attribute data-test-value will be accessible via HTMLElement.dataset.testValue as any dash * attribute data-test-value will be accessible via HTMLElement.dataset.testValue as any dash
* (U+002D) is replaced by the capitalization of the next letter (camelcase). * (U+002D) is replaced by the capitalization of the next letter (camelcase).
*/ */
def dataAttr(suffix: String): HtmlAttr[String] = new HtmlAttr(s"data-$suffix", StringAsIsCodec) def dataAttr(suffix: String): HtmlAttr[String] = new HtmlAttr(s"data-$suffix", StringAsIsEncoder)
/** This attribute contains CSS styling declarations to be applied to the element. Note that it is /** This attribute contains CSS styling declarations to be applied to the element. Note that it is
* recommended for styles to be defined in a separate file or files. This attribute and the style * recommended for styles to be defined in a separate file or files. This attribute and the style
* element have mainly the purpose of allowing for quick styling, for example for testing * element have mainly the purpose of allowing for quick styling, for example for testing
* purposes. * purposes.
*/ */
lazy val styleAttr: HtmlAttr[String] = new HtmlAttr("style", StringAsIsCodec) lazy val styleAttr: HtmlAttr[String] = new HtmlAttr("style", StringAsIsEncoder)
end ComplexHtmlKeys end ComplexHtmlKeys