Initial Commit

This commit is contained in:
DavidOnTop 2024-09-18 21:14:02 +02:00
commit a679aeb978
No known key found for this signature in database
GPG key ID: 5D05538A45D5149F
27 changed files with 823 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
shared/src/main/scala/top/davidon/sfs/dom/defs
target/

6
.scalafmt.conf Normal file
View file

@ -0,0 +1,6 @@
version = 3.8.3
runner.dialect = scala3
rewrite.scala3.newSyntax.control = true
rewrite.scala3.newSyntax.deprecated = true
rewrite.scala3.removeOptionalBraces.enabled = false
rewrite.scala3.convertToNewSyntax = true

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 David Kozak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
build.sbt Normal file
View file

@ -0,0 +1,26 @@
ThisBuild / version := "0.1.0"
ThisBuild / organization := "top.davidon"
ThisBuild / scalaVersion := "3.5.0"
lazy val precompile = taskKey[Unit]("pre compilation tasks")
precompile := DomGenerator.generate()
(Compile / compile) := ((Compile / compile) dependsOn precompile).value
lazy val root = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Full)
.in(file("."))
.settings(
name := "sfs",
libraryDependencies ++= Seq(
"de.tu-darmstadt.stg" %%% "reactives" % "0.36.0",
"org.scala-js" % "scalajs-dom" % "2.8.0" % "sjs1_3"
)
)
.jvmSettings(
libraryDependencies ++= Seq(
"org.scala-js" % "scalajs-dom_sjs1_3" % "2.8.0"
)
)

367
project/DomGenerator.scala Normal file
View file

@ -0,0 +1,367 @@
import com.raquo.domtypes.codegen.*
import com.raquo.domtypes.codegen.DefType.LazyVal
import com.raquo.domtypes.common.{HtmlTagType, SvgTagType}
object DomGenerator {
def generate(): Unit = {
val defGroups = new CanonicalDefGroups()
{
val traitName = "HtmlTags"
val fileContent = generator.generateTagsTrait(
tagType = HtmlTagType,
defGroups = defGroups.htmlTagsDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitModifiers = Nil,
traitName = traitName,
keyKind = "HtmlTag",
baseImplDefComments = List(
"Create HTML tag",
"",
"Note: this simply creates an instance of HtmlTag.",
" - This does not create the element (to do that, call .apply() on the returned tag instance)",
" - This does not register this tag name as a custom element",
" - See https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements",
"",
"@param name - e.g. \"div\" or \"mwc-input\"",
"",
"@tparam Ref - type of elements with this tag, e.g. dom.html.Input for \"input\" tag"
),
keyImplName = "htmlTag",
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.tagDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
{
val traitName = "SvgTags"
val fileContent = generator.generateTagsTrait(
tagType = SvgTagType,
defGroups = defGroups.svgTagsDefGroups,
printDefGroupComments = false,
traitCommentLines = Nil,
traitModifiers = Nil,
traitName = traitName,
keyKind = "SvgTag",
baseImplDefComments = List(
"Create SVG tag",
"",
"Note: this simply creates an instance of HtmlTag.",
" - This does not create the element (to do that, call .apply() on the returned tag instance)",
"",
"@param name - e.g. \"circle\"",
"",
"@tparam Ref - type of elements with this tag, e.g. dom.svg.Circle for \"circle\" tag"
),
keyImplName = "svgTag",
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.tagDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
{
val traitName = "HtmlAttrs"
val fileContent = generator.generateAttrsTrait(
defGroups = defGroups.htmlAttrDefGroups,
printDefGroupComments = false,
traitCommentLines = Nil,
traitModifiers = Nil,
traitName = traitName,
keyKind = "HtmlAttr",
implNameSuffix = "HtmlAttr",
baseImplDefComments = List(
"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",
"",
"@tparam V - value type for this attr in Scala"
),
baseImplName = "htmlAttr",
namespaceImports = Nil,
namespaceImpl = _ => ???,
transformAttrDomName = identity,
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.attrDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
{
val traitName = "SvgAttrs"
val fileContent = generator.generateAttrsTrait(
defGroups = defGroups.svgAttrDefGroups,
printDefGroupComments = false,
traitModifiers = Nil,
traitName = traitName,
traitCommentLines = Nil,
keyKind = "SvgAttr",
baseImplDefComments = List(
"Create SVG attribute (Note: for HTML attrs, use L.htmlAttr)",
"",
"@param name - 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"
),
implNameSuffix = "SvgAttr",
baseImplName = "svgAttr",
namespaceImports = Nil,
namespaceImpl = SourceRepr(_),
transformAttrDomName = identity,
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.attrDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
{
val traitName = "AriaAttrs"
def transformAttrDomName(ariaAttrName: String): String = {
// format: off
if (ariaAttrName.startsWith("aria-")) {
ariaAttrName.substring(5)
} else {
throw new Exception(
s"Aria attribute does not start with `aria-`: $ariaAttrName"
)
}
// format: on
}
val fileContent = generator.generateAttrsTrait(
defGroups = defGroups.ariaAttrDefGroups,
printDefGroupComments = false,
traitModifiers = Nil,
traitName = traitName,
traitCommentLines = Nil,
keyKind = "AriaAttr",
implNameSuffix = "AriaAttr",
baseImplDefComments = List(
"Create ARIA attribute (Note: for HTML attrs, use L.htmlAttr)",
"",
"@param name - 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"
),
baseImplName = "ariaAttr",
namespaceImports = Nil,
namespaceImpl = _ => ???,
transformAttrDomName = transformAttrDomName,
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.attrDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
{
val traitName = "HtmlProps"
val fileContent = generator.generatePropsTrait(
defGroups = defGroups.propDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitModifiers = Nil,
traitName = traitName,
keyKind = "HtmlProp",
implNameSuffix = "Prop",
baseImplDefComments = List(
"Create custom HTML element property",
"",
"@param name - name of the prop in JS, e.g. \"value\"",
"@param codec - used to encode V into DomV, e.g. StringAsIsCodec,",
"",
"@tparam V - value type for this prop in Scala",
"@tparam DomV - value type for this prop in the underlying JS DOM."
),
baseImplName = "htmlProp",
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.propDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
{
val baseTraitName = "GlobalEventProps"
val subTraits = List(
"WindowEventProps" -> defGroups.windowEventPropDefGroups,
"DocumentEventProps" -> defGroups.documentEventPropDefGroups
)
{
val fileContent = generator.generateEventPropsTrait(
defSources = defGroups.globalEventPropDefGroups,
printDefGroupComments = true,
traitCommentLines = Nil,
traitModifiers = Nil,
traitName = baseTraitName,
traitExtends = Nil,
traitThisType = None,
baseImplDefComments = List(
"Create custom event property",
"",
"@param name - event type in JS, e.g. \"click\"",
"",
"@tparam Ev - event type in JS, e.g. dom.MouseEvent"
),
outputBaseImpl = true,
keyKind = "EventProp",
keyImplName = "eventProp",
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.eventPropDefsPackagePath,
fileName = baseTraitName,
fileContent = fileContent
)
}
subTraits.foreach { case (traitName, eventPropsDefGroups) =>
val fileContent = generator.generateEventPropsTrait(
defSources = eventPropsDefGroups,
printDefGroupComments = true,
traitCommentLines = List(eventPropsDefGroups.head._1),
traitModifiers = Nil,
traitName = traitName,
traitExtends = Nil,
traitThisType = Some(baseTraitName),
baseImplDefComments = Nil,
outputBaseImpl = false,
keyKind = "EventProp",
keyImplName = "eventProp",
defType = LazyVal
)
generator.writeToFile(
packagePath = generator.eventPropDefsPackagePath,
fileName = traitName,
fileContent = fileContent
)
}
}
// -- Style props --
// {
// val traitName = "StyleProps"
//
// val fileContent = generator.generateStylePropsTrait(
// defSources = defGroups.stylePropDefGroups,
// printDefGroupComments = true,
// traitCommentLines = Nil,
// traitModifiers = Nil,
// traitName = traitName,
// keyKind = "StyleProp",
// keyKindAlias = "StyleProp",
// setterType = "StyleSetter",
// setterTypeAlias = "SS",
// derivedKeyKind = "DerivedStyleProp",
// derivedKeyKindAlias = "DSP",
// baseImplDefComments = List(
// "Create custom CSS property",
// "",
// "@param name - name of CSS property, e.g. \"font-weight\"",
// "",
// "@tparam V - type of values recognized by JS for this property, e.g. Int",
// " Note: String is always allowed regardless of the type you put here.",
// " If unsure, use String type as V."
// ),
// baseImplName = "styleProp",
// defType = LazyVal,
// lengthUnitsNumType = "Int",
// outputUnitTraits = true
// )
//
// generator.writeToFile(
// packagePath = generator.stylePropDefsPackagePath,
// fileName = traitName,
// fileContent = fileContent
// )
// }
// -- Style keyword traits
// {
// StyleTraitDefs.defs.foreach { styleTrait =>
// val fileContent = generator.generateStyleKeywordsTrait(
// defSources = styleTrait.keywordDefGroups,
// printDefGroupComments = styleTrait.keywordDefGroups.length > 1,
// traitCommentLines = Nil,
// traitModifiers = Nil,
// traitName = styleTrait.scalaName.replace("[_]", ""),
// extendsTraits = styleTrait.extendsTraits.map(_.replace("[_]", "")),
// extendsUnitTraits = styleTrait.extendsUnits,
// propKind = "StyleProp",
// keywordType = "StyleSetter",
// derivedKeyKind = "DerivedStyleProp",
// lengthUnitsNumType = "Int",
// defType = LazyVal,
// outputUnitTypes = true,
// allowSuperCallInOverride = false // can't access lazy val from `super`
// )
//
// generator.writeToFile(
// packagePath = generator.styleTraitsPackagePath(),
// fileName = styleTrait.scalaName.replace("[_]", ""),
// fileContent = fileContent
// )
// }
// }
}
private object generator
extends CanonicalGenerator(
baseOutputDirectoryPath = "shared/src/main/scala/top/davidon/sfs/dom",
basePackagePath = "top.davidon.sfs.dom",
standardTraitCommentLines = List(
"#NOTE: GENERATED CODE",
s" - 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."
),
format = CodeFormatting()
) {
override def settersPackagePath: String =
basePackagePath + ".modifiers.KeySetter"
override def scalaJsElementTypeParam: String = "Ref"
}
}

1
project/build.properties Normal file
View file

@ -0,0 +1 @@
sbt.version = 1.10.1

6
project/build.sbt Normal file
View file

@ -0,0 +1,6 @@
lazy val root = (project in file("."))
.settings(
libraryDependencies ++= Seq(
"com.raquo" %% "domtypes" % "18.1.0"
)
)

2
project/plugins.sbt Normal file
View file

@ -0,0 +1,2 @@
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")

View file

@ -0,0 +1,18 @@
package top.davidon.sfs.dom.defs.complex
import top.davidon.sfs.dom.codecs
import top.davidon.sfs.dom.keys.HtmlAttr
trait ComplexHtmlKeys {
lazy val rel = HtmlAttr("rel", codecs.StringAsIsCodec)
lazy val role = HtmlAttr("role", codecs.StringAsIsCodec)
lazy val styleAttr = HtmlAttr("style", codecs.StringAsIsCodec)
val className = HtmlAttr("class", codecs.StringAsIsCodec)
val cls: HtmlAttr[String] = className
val `class`: HtmlAttr[String] = className
val classList = HtmlAttr("class", codecs.IterableAsSpaceSeparatedStringCodec)
val cl: HtmlAttr[Iterable[String]] = classList
def dataAttr(suffix: String): HtmlAttr[String] =
HtmlAttr[String]("data-" + suffix, codecs.StringAsIsCodec)
}

View file

@ -0,0 +1,13 @@
package top.davidon.sfs.dom.defs.complex
import top.davidon.sfs.dom.codecs
import top.davidon.sfs.dom.keys.HtmlAttr
trait ComplexSvgKeys {
lazy val role = HtmlAttr("role", codecs.StringAsIsCodec)
val className = HtmlAttr("class", codecs.StringAsIsCodec)
val cls: HtmlAttr[String] = className
val `class`: HtmlAttr[String] = className
val classList = HtmlAttr("class", codecs.IterableAsSpaceSeparatedStringCodec)
val cl: HtmlAttr[Iterable[String]] = classList
}

View file

@ -0,0 +1,10 @@
package top.davidon.sfs.dom
import top.davidon.sfs.dom.tags.Tag
/** tag + modifiers + value */
class Element[+Ref <: org.scalajs.dom.Element](
val tag: Tag[Ref],
val mods: Iterable[Modifier[?, ?]],
val value: Value[?, String]
) {}

View file

@ -0,0 +1,6 @@
package top.davidon.sfs.dom
import top.davidon.sfs.dom.keys.Key
import top.davidon.sfs.dom.Value
class Modifier[F, T](val key: Key, val value: Value[F, T]) {}

View file

@ -0,0 +1,7 @@
package top.davidon.sfs.dom
import org.scalajs.dom
trait Renderer[T] {
def render(elements: Element[dom.Element]*): T
}

View file

@ -0,0 +1,3 @@
package top.davidon.sfs.dom
object SFS extends ScalaFullStack {}

View file

@ -0,0 +1,58 @@
package top.davidon.sfs.dom
import top.davidon.sfs.dom.codecs.*
import top.davidon.sfs.dom.defs.attrs.{AriaAttrs, HtmlAttrs, SvgAttrs}
import top.davidon.sfs.dom.defs.complex.{ComplexHtmlKeys, ComplexSvgKeys}
import top.davidon.sfs.dom.defs.eventProps.GlobalEventProps
import top.davidon.sfs.dom.defs.props.HtmlProps
import top.davidon.sfs.dom.defs.tags.{HtmlTags, SvgTags}
trait ScalaFullStack
extends HtmlTags
with HtmlAttrs
with HtmlProps
with GlobalEventProps
with ComplexHtmlKeys {
type Component = Element[?]
object aria extends AriaAttrs
object svg extends SvgTags with SvgAttrs with ComplexSvgKeys
given AsValue[String, String] with {
extension (from: String) {
def asStringValue(): Value[String, String] = {
Value(from, StringAsIsCodec)
}
}
}
given AsValue[Int, String] with {
extension (from: Int) {
def asStringValue(): Value[Int, String] = {
Value(from, IntAsStringCodec)
}
}
}
given AsValue[Double, String] with {
extension (from: Double) {
def asStringValue(): Value[Double, String] = {
Value(from, DoubleAsStringCodec)
}
}
}
given AsValue[Boolean, String] with {
extension (from: Boolean) {
def asStringValue(): Value[Boolean, String] = {
Value(from, BooleanAsTrueFalseStringCodec)
}
}
}
given AsValue[Iterable[String], String] with {
extension (from: Iterable[String]) {
def asStringValue(): Value[Iterable[String], String] = {
Value(from, IterableAsSpaceSeparatedStringCodec)
}
}
}
}

View file

@ -0,0 +1,37 @@
package top.davidon.sfs.dom
import top.davidon.sfs.dom.codecs.{Codec, StringAsIsCodec}
class Value[F, T](
val value: F,
val codec: Codec[F, T],
var isReactive: Boolean = false
) {
def apply(): T = {
codec.encode(value)
}
def reactive(value: Boolean = true): Value[F, T] = {
isReactive = value
this
}
}
trait AsValue[F, T] {
extension (from: F) {
def asStringValue(): Value[F, String]
}
}
object Value {
def join(
iterator: Iterable[Value[?, String]]
): Value[String, String] = {
Value(
iterator.map(v => v.codec.encode(v.value)).mkString(""),
StringAsIsCodec,
iterator.exists(_.isReactive)
)
}
}

View file

@ -0,0 +1,25 @@
package top.davidon.sfs.dom.codecs
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
}

View file

@ -0,0 +1,87 @@
package top.davidon.sfs.dom
package object codecs {
lazy val IntAsStringCodec: Codec[Int, String] = new 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
}
lazy val DoubleAsIsCodec: Codec[Double, Double] = AsIsCodec()
lazy val DoubleAsStringCodec: Codec[Double, String] =
new 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
}
lazy val BooleanAsTrueFalseStringCodec: Codec[Boolean, String] =
new Codec[Boolean, String] {
override def decode(domValue: String): Boolean = domValue == "true"
override def encode(scalaValue: Boolean): String =
if scalaValue then "true" else "false"
}
lazy val BooleanAsYesNoStringCodec: Codec[Boolean, String] =
new Codec[Boolean, String] {
override def decode(domValue: String): Boolean = domValue == "yes"
override def encode(scalaValue: Boolean): String =
if scalaValue then "yes" else "no"
}
lazy val BooleanAsOnOffStringCodec: Codec[Boolean, String] =
new Codec[Boolean, String] {
override def decode(domValue: String): Boolean = domValue == "on"
override def encode(scalaValue: Boolean): String =
if scalaValue then "on" else "off"
}
lazy val IterableAsSpaceSeparatedStringCodec
: Codec[Iterable[String], String] =
new Codec[Iterable[String], String] { // could 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(" ")
}
lazy val IterableAsCommaSeparatedStringCodec
: Codec[Iterable[String], String] =
new Codec[Iterable[String], String] { // could 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(",")
}
val StringAsIsCodec: Codec[String, String] = AsIsCodec()
val IntAsIsCodec: Codec[Int, Int] = AsIsCodec()
val BooleanAsIsCodec: Codec[Boolean, Boolean] = AsIsCodec()
val BooleanAsAttrPresenceCodec: Codec[Boolean, String] =
new Codec[Boolean, String] {
override def decode(domValue: String): Boolean = domValue != null
override def encode(scalaValue: Boolean): String =
if scalaValue then "" else null
}
def AsIsCodec[V](): Codec[V, V] = new Codec[V, V] {
override def encode(scalaValue: V): V = scalaValue
override def decode(domValue: V): V = domValue
}
}

View file

@ -0,0 +1,18 @@
package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.{Modifier, Value}
class AriaAttr[V](
suffix: String,
val codec: Codec[V, String]
) extends Key {
override val name: String = "aria-" + suffix
@inline def apply(value: V): Modifier[V, String] = {
this := value
}
def :=(value: V): Modifier[V, String] = {
Modifier(this, Value(value, codec))
}
}

View file

@ -0,0 +1,5 @@
package top.davidon.sfs.dom.keys
import org.scalajs.dom
class EventProp[Event <: dom.Event](override val name: String) extends Key {}

View file

@ -0,0 +1,17 @@
package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.{Modifier, Value}
class HtmlAttr[V](
override val name: String,
val codec: Codec[V, String]
) extends Key {
@inline def apply(value: V): Modifier[V, String] = {
this := value
}
def :=(value: V): Modifier[V, String] = {
Modifier(this, Value(value, codec))
}
}

View file

@ -0,0 +1,16 @@
package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.{Modifier, Value}
class HtmlProp[V, DomV](
override val name: String,
val codec: Codec[V, DomV]
) extends Key {
@inline def apply(value: V): Modifier[V, DomV] = {
this := value
}
def :=(value: V): Modifier[V, DomV] = {
Modifier(this, Value(value, codec))
}
}

View file

@ -0,0 +1,5 @@
package top.davidon.sfs.dom.keys
abstract class Key {
val name: String
}

View file

@ -0,0 +1,38 @@
package top.davidon.sfs.dom.keys
import top.davidon.sfs.dom.codecs.Codec
import top.davidon.sfs.dom.{Modifier, Value}
class SvgAttr[V](
val localName: String,
val codec: Codec[V, String],
val namespacePrefix: Option[String]
) extends Key {
override val name: String =
namespacePrefix.map(_ + ":" + localName).getOrElse(localName)
val namespaceUri: Option[String] = namespacePrefix.map(SvgAttr.namespaceUri)
@inline def apply(value: V): Modifier[V, String] = {
this := value
}
def :=(value: V): Modifier[V, String] = {
Modifier(this, Value(value, codec))
}
}
object SvgAttr {
final val svgNamespaceUri: String = "http://www.w3.org/2000/svg"
final val xlinkNamespaceUri: String = "http://www.w3.org/1999/xlink"
final val xmlNamespaceUri: String = "http://www.w3.org/XML/1998/namespace"
final val xmlnsNamespaceUri: String = "http://www.w3.org/2000/xmlns/"
final def namespaceUri(namespace: String): String = {
namespace match {
case "svg" => svgNamespaceUri
case "xlink" => xlinkNamespaceUri
case "xml" => xmlNamespaceUri
case "xmlns" => xmlnsNamespaceUri
}
}
}

View file

@ -0,0 +1,6 @@
package top.davidon.sfs.dom.tags
class HtmlTag[+Ref <: org.scalajs.dom.html.Element](
override val name: String,
override val void: Boolean = false
) extends Tag[Ref] {}

View file

@ -0,0 +1,6 @@
package top.davidon.sfs.dom.tags
class SvgTag[+Ref <: org.scalajs.dom.svg.Element](
override val name: String,
override val void: Boolean = false
) extends Tag[Ref] {}

View file

@ -0,0 +1,17 @@
package top.davidon.sfs.dom.tags
import org.scalajs.dom
import top.davidon.sfs.dom.{Element, Modifier, Value}
trait Tag[+Ref <: dom.Element] {
val name: String
val void: Boolean
def apply(
modifiers: Modifier[?, ?]*
)(
values: Value[?, String]*
): Element[Ref] = {
Element[Ref](this, modifiers, Value.join(values))
}
}