Initial Commit
This commit is contained in:
commit
a679aeb978
27 changed files with 823 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
shared/src/main/scala/top/davidon/sfs/dom/defs
|
||||||
|
target/
|
6
.scalafmt.conf
Normal file
6
.scalafmt.conf
Normal 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
21
LICENSE
Normal 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
26
build.sbt
Normal 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
367
project/DomGenerator.scala
Normal 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
1
project/build.properties
Normal file
|
@ -0,0 +1 @@
|
||||||
|
sbt.version = 1.10.1
|
6
project/build.sbt
Normal file
6
project/build.sbt
Normal 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
2
project/plugins.sbt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
|
||||||
|
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
|
18
shared/src/main/scala/ComplexHtmlKeys.scala
Normal file
18
shared/src/main/scala/ComplexHtmlKeys.scala
Normal 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)
|
||||||
|
}
|
13
shared/src/main/scala/ComplexSvgKeys.scala
Normal file
13
shared/src/main/scala/ComplexSvgKeys.scala
Normal 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
|
||||||
|
}
|
10
shared/src/main/scala/top/davidon/sfs/dom/Element.scala
Normal file
10
shared/src/main/scala/top/davidon/sfs/dom/Element.scala
Normal 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]
|
||||||
|
) {}
|
6
shared/src/main/scala/top/davidon/sfs/dom/Modifier.scala
Normal file
6
shared/src/main/scala/top/davidon/sfs/dom/Modifier.scala
Normal 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]) {}
|
7
shared/src/main/scala/top/davidon/sfs/dom/Renderer.scala
Normal file
7
shared/src/main/scala/top/davidon/sfs/dom/Renderer.scala
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package top.davidon.sfs.dom
|
||||||
|
|
||||||
|
import org.scalajs.dom
|
||||||
|
|
||||||
|
trait Renderer[T] {
|
||||||
|
def render(elements: Element[dom.Element]*): T
|
||||||
|
}
|
3
shared/src/main/scala/top/davidon/sfs/dom/SFS.scala
Normal file
3
shared/src/main/scala/top/davidon/sfs/dom/SFS.scala
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package top.davidon.sfs.dom
|
||||||
|
|
||||||
|
object SFS extends ScalaFullStack {}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
shared/src/main/scala/top/davidon/sfs/dom/Value.scala
Normal file
37
shared/src/main/scala/top/davidon/sfs/dom/Value.scala
Normal 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
5
shared/src/main/scala/top/davidon/sfs/dom/keys/Key.scala
Normal file
5
shared/src/main/scala/top/davidon/sfs/dom/keys/Key.scala
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package top.davidon.sfs.dom.keys
|
||||||
|
|
||||||
|
abstract class Key {
|
||||||
|
val name: String
|
||||||
|
}
|
38
shared/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala
Normal file
38
shared/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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] {}
|
|
@ -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] {}
|
17
shared/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala
Normal file
17
shared/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue