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