feat: project seperation
This commit is contained in:
parent
a679aeb978
commit
3e06f86735
24 changed files with 49 additions and 21 deletions
18
dom/src/main/scala/ComplexHtmlKeys.scala
Normal file
18
dom/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
dom/src/main/scala/ComplexSvgKeys.scala
Normal file
13
dom/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
dom/src/main/scala/top/davidon/sfs/dom/Element.scala
Normal file
10
dom/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
dom/src/main/scala/top/davidon/sfs/dom/Modifier.scala
Normal file
6
dom/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.Value
|
||||
import top.davidon.sfs.dom.keys.Key
|
||||
|
||||
class Modifier[F, T](val key: Key, val value: Value[F, T]) {}
|
7
dom/src/main/scala/top/davidon/sfs/dom/Renderer.scala
Normal file
7
dom/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
dom/src/main/scala/top/davidon/sfs/dom/SFS.scala
Normal file
3
dom/src/main/scala/top/davidon/sfs/dom/SFS.scala
Normal file
|
@ -0,0 +1,3 @@
|
|||
package top.davidon.sfs.dom
|
||||
|
||||
object SFS extends ScalaFullStack {}
|
58
dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStack.scala
Normal file
58
dom/src/main/scala/top/davidon/sfs/dom/ScalaFullStack.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
dom/src/main/scala/top/davidon/sfs/dom/Value.scala
Normal file
37
dom/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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
25
dom/src/main/scala/top/davidon/sfs/dom/codecs/Codecs.scala
Normal file
25
dom/src/main/scala/top/davidon/sfs/dom/codecs/Codecs.scala
Normal 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
|
||||
}
|
87
dom/src/main/scala/top/davidon/sfs/dom/codecs/package.scala
Normal file
87
dom/src/main/scala/top/davidon/sfs/dom/codecs/package.scala
Normal 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
|
||||
}
|
||||
}
|
18
dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala
Normal file
18
dom/src/main/scala/top/davidon/sfs/dom/keys/AriaAttr.scala
Normal 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))
|
||||
}
|
||||
}
|
|
@ -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 {}
|
17
dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala
Normal file
17
dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlAttr.scala
Normal 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))
|
||||
}
|
||||
}
|
16
dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala
Normal file
16
dom/src/main/scala/top/davidon/sfs/dom/keys/HtmlProp.scala
Normal 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))
|
||||
}
|
||||
}
|
5
dom/src/main/scala/top/davidon/sfs/dom/keys/Key.scala
Normal file
5
dom/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
dom/src/main/scala/top/davidon/sfs/dom/keys/SvgAttr.scala
Normal file
38
dom/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] {}
|
6
dom/src/main/scala/top/davidon/sfs/dom/tags/SvgTag.scala
Normal file
6
dom/src/main/scala/top/davidon/sfs/dom/tags/SvgTag.scala
Normal 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] {}
|
17
dom/src/main/scala/top/davidon/sfs/dom/tags/Tag.scala
Normal file
17
dom/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