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
).printTrait().getOutput()
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
def generate(): Unit =
@ -134,7 +198,7 @@ class DomDefsGenerator(baseOutputDirectoryPath: String):
"Create HTML attribute (Note: for SVG attrs, use L.svg.svgAttr)",
"",
"@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"
),

View file

@ -2,8 +2,8 @@ package scalive
import scalive.Mod.Attr
import scalive.Mod.Content
import scalive.codecs.BooleanAsAttrPresenceCodec
import scalive.codecs.Codec
import scalive.codecs.BooleanAsAttrPresenceEncoder
import scalive.codecs.Encoder
import zio.json.*
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]):
private inline def isBooleanAsAttrPresence = codec == BooleanAsAttrPresenceCodec
class HtmlAttr[V](val name: String, val codec: Encoder[V, String]):
private inline def isBooleanAsAttrPresence = codec == BooleanAsAttrPresenceEncoder
def :=(value: V): Mod.Attr =
if isBooleanAsAttrPresence then

View file

@ -1,12 +1,15 @@
import scalive.codecs.BooleanAsAttrPresenceCodec
import scalive.codecs.StringAsIsCodec
import scalive.codecs.BooleanAsAttrPresenceEncoder
import scalive.codecs.BooleanAsTrueFalseStringEncoder
import scalive.codecs.Encoder
import scalive.codecs.IntAsStringEncoder
import scalive.codecs.StringAsIsEncoder
import scalive.defs.attrs.HtmlAttrs
import scalive.defs.complex.ComplexHtmlKeys
import scalive.defs.tags.HtmlTags
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:
def navigate(path: String, mods: Mod*): HtmlElement =
@ -14,20 +17,76 @@ package object scalive extends HtmlTags with HtmlAttrs with ComplexHtmlKeys:
object phx:
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 =
new HtmlAttrJsonValue(s"phx-$suffix")
private def dataPhxAttr(suffix: String): HtmlAttr[String] =
dataAttr(s"phx-$suffix")
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 linkState = dataPhxAttr("link-state")
// 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")
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 htmlElementToMod(el: HtmlElement): Mod = Mod.Content.Tag(el)
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
* document.getElementsByClassName
*/
val className: HtmlAttr[String] = new HtmlAttr("class", StringAsIsCodec)
val className: HtmlAttr[String] = new HtmlAttr("class", StringAsIsEncoder)
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
* 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.
* 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]]
*/
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
* 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
* (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
* 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
* purposes.
*/
lazy val styleAttr: HtmlAttr[String] = new HtmlAttr("style", StringAsIsCodec)
lazy val styleAttr: HtmlAttr[String] = new HtmlAttr("style", StringAsIsEncoder)
end ComplexHtmlKeys