All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.shogowada.scalajs.reactjs.VirtualDOM.scala Maven / Gradle / Ivy

The newest version!
package io.github.shogowada.scalajs.reactjs

import io.github.shogowada.scalajs.reactjs.VirtualDOM.VirtualDOMAttributes.Type.AS_IS
import io.github.shogowada.scalajs.reactjs.classes.ReactClass
import io.github.shogowada.scalajs.reactjs.classes.specs.ReactClassSpec
import io.github.shogowada.scalajs.reactjs.elements.{ReactElement, ReactHTMLElement}
import io.github.shogowada.scalajs.reactjs.events._
import io.github.shogowada.statictags.AttributeValueType.AttributeValueType
import io.github.shogowada.statictags._

import scala.language.implicitConversions
import scala.scalajs.js
import scala.scalajs.js.JSConverters._

/** Factory for virtual DOMs
  *
  * Virtual DOMs have type of [[Element]], which are implicitly converted to [[ReactElement]].
  *
  * Import VirtualDOM._ and access factory methods for DOMs with {{{<}}} and attributes with {{{^}}}.
  * {{{
  * import io.github.shogowada.scalajs.reactjs.VirtualDOM._
  *
  * object Foo {
  *   def render(): ReactElement = <.div(^.id := "main")(
  *     <.div()("first child"),
  *     <.div()("second child")
  *   )
  * }
  * }}}
  * */
object VirtualDOM extends VirtualDOM

trait EventVirtualDOMAttributes {

  trait OnEventAttribute[Event <: SyntheticEvent] extends AttributeSpec {
    val name: String

    def :=(callback: js.Function0[_]): Attribute[js.Function0[_]] = {
      Attribute(name, callback, AS_IS)
    }

    def :=(callback: js.Function1[Event, _]): Attribute[js.Function1[Event, _]] = {
      Attribute(name, callback, AS_IS)
    }
  }

  // Animation Events
  case class OnAnimationEventAttribute(name: String) extends OnEventAttribute[AnimationSyntheticEvent]

  lazy val onAnimationStart = OnAnimationEventAttribute("onAnimationStart")
  lazy val onAnimationEnd = OnAnimationEventAttribute("onAnimationEnd")
  lazy val onAnimationIteration = OnAnimationEventAttribute("onAnimationIteration")

  // Clipboard Events
  case class OnClipboardEventAttribute(name: String) extends OnEventAttribute[ClipboardSyntheticEvent]

  lazy val onCopy = OnClipboardEventAttribute("onCopy")
  lazy val onCut = OnClipboardEventAttribute("onCut")
  lazy val onPaste = OnClipboardEventAttribute("onPaste")

  // Composition Events
  case class OnCompositionEventAttribute(name: String) extends OnEventAttribute[CompositionSyntheticEvent]

  lazy val onCompositionEnd = OnCompositionEventAttribute("onCompositionEnd")
  lazy val onCompositionStart = OnCompositionEventAttribute("onCompositionStart")
  lazy val onCompositionUpdate = OnCompositionEventAttribute("onCompositionUpdate")

  // Focus Events
  case class OnFocusEventAttribute(name: String) extends OnEventAttribute[FocusSyntheticEvent]

  lazy val onFocus = OnFocusEventAttribute("onFocus")
  lazy val onBlur = OnFocusEventAttribute("onBlur")

  // Form Events
  case class OnFormEventAttribute[FormEvent <: FormSyntheticEvent[_]](name: String) extends OnEventAttribute[FormEvent]

  lazy val onChange = OnFormEventAttribute("onChange")
  lazy val onInput = OnFormEventAttribute("onInput")
  lazy val onSubmit = OnFormEventAttribute("onSubmit")

  // Image Events
  case class OnImageEventAttribute(name: String) extends OnEventAttribute[ImageSyntheticEvent]

  lazy val onLoad = OnImageEventAttribute("onLoad")

  // onError conflicts with Media Events. Conflicts are treated specially at the bottom of this trait.
  // lazy val onError = OnImageEventAttribute("onError")

  // Keyboard Events
  case class OnKeyboardEventAttribute(name: String) extends OnEventAttribute[KeyboardSyntheticEvent]

  lazy val onKeyDown = OnKeyboardEventAttribute("onKeyDown")
  lazy val onKeyPress = OnKeyboardEventAttribute("onKeyPress")
  lazy val onKeyUp = OnKeyboardEventAttribute("onKeyUp")

  // Media Events
  case class OnMediaEventAttribute(name: String) extends OnEventAttribute[MediaSyntheticEvent]

  lazy val onAbort = OnMediaEventAttribute("onAbort")
  lazy val onCanPlay = OnMediaEventAttribute("onCanPlay")
  lazy val onCanPlayThrough = OnMediaEventAttribute("onCanPlayThrough")
  lazy val onDurationChange = OnMediaEventAttribute("onDurationChange")
  lazy val onEmptied = OnMediaEventAttribute("onEmptied")
  lazy val onEncrypted = OnMediaEventAttribute("onEncrypted")
  lazy val onEnded = OnMediaEventAttribute("onEnded")
  // onError conflicts with Image Events. Conflicts are treated specially at the bottom of this trait.
  // lazy val onError = OnMediaEventAttribute("onError")
  lazy val onLoadedData = OnMediaEventAttribute("onLoadedData")
  lazy val onLoadedMetadata = OnMediaEventAttribute("onLoadedMetadata")
  lazy val onLoadStart = OnMediaEventAttribute("onLoadStart")
  lazy val onPause = OnMediaEventAttribute("onPause")
  lazy val onPlay = OnMediaEventAttribute("onPlay")
  lazy val onPlaying = OnMediaEventAttribute("onPlaying")
  lazy val onProgress = OnMediaEventAttribute("onProgress")
  lazy val onRateChange = OnMediaEventAttribute("onRateChange")
  lazy val onSeeked = OnMediaEventAttribute("onSeeked")
  lazy val onSeeking = OnMediaEventAttribute("onSeeking")
  lazy val onStalled = OnMediaEventAttribute("onStalled")
  lazy val onSuspend = OnMediaEventAttribute("onSuspend")
  lazy val onTimeUpdate = OnMediaEventAttribute("onTimeUpdate")
  lazy val onVolumeChange = OnMediaEventAttribute("onVolumeChange")
  lazy val onWaiting = OnMediaEventAttribute("onWaiting")

  // Mouse Events
  case class OnMouseEventAttribute(name: String) extends OnEventAttribute[MouseSyntheticEvent]

  lazy val onClick = OnMouseEventAttribute("onClick")
  lazy val onContextMenu = OnMouseEventAttribute("onContextMenu")
  lazy val onDoubleClick = OnMouseEventAttribute("onDoubleClick")
  lazy val onDrag = OnMouseEventAttribute("onDrag")
  lazy val onDragEnd = OnMouseEventAttribute("onDragEnd")
  lazy val onDragEnter = OnMouseEventAttribute("onDragEnter")
  lazy val onDragExit = OnMouseEventAttribute("onDragExit")
  lazy val onDragLeave = OnMouseEventAttribute("onDragLeave")
  lazy val onDragOver = OnMouseEventAttribute("onDragOver")
  lazy val onDragStart = OnMouseEventAttribute("onDragStart")
  lazy val onDrop = OnMouseEventAttribute("onDrop")
  lazy val onMouseDown = OnMouseEventAttribute("onMouseDown")
  lazy val onMouseEnter = OnMouseEventAttribute("onMouseEnter")
  lazy val onMouseLeave = OnMouseEventAttribute("onMouseLeave")
  lazy val onMouseMove = OnMouseEventAttribute("onMouseMove")
  lazy val onMouseOut = OnMouseEventAttribute("onMouseOut")
  lazy val onMouseOver = OnMouseEventAttribute("onMouseOver")
  lazy val onMouseUp = OnMouseEventAttribute("onMouseUp")

  // Selection Events
  case class OnSelectionEventAttribute(name: String) extends OnEventAttribute[SelectionSyntheticEvent]

  lazy val onSelect = OnSelectionEventAttribute("onSelect")

  // Touch Events
  case class OnTouchEventAttribute(name: String) extends OnEventAttribute[TouchSyntheticEvent]

  lazy val onTouchCancel = OnTouchEventAttribute("onTouchCancel")
  lazy val onTouchEnd = OnTouchEventAttribute("onTouchEnd")
  lazy val onTouchMove = OnTouchEventAttribute("onTouchMove")
  lazy val onTouchStart = OnTouchEventAttribute("onTouchStart")

  // Transition Events
  case class OnTransitionEventAttribute(name: String) extends OnEventAttribute[TransitionSyntheticEvent]

  lazy val onTransitionEnd = OnTransitionEventAttribute("onTransitionEnd")

  // UI Events
  case class OnUIEventAttribute(name: String) extends OnEventAttribute[UISyntheticEvent]

  lazy val onScroll = OnUIEventAttribute("onScroll")

  // Wheel Events
  case class OnWheelEventAttribute(name: String) extends OnEventAttribute[WheelSyntheticEvent]

  lazy val onWheel = OnWheelEventAttribute("onWheel")

  // Events that conflicted with other events
  case class OnErrorEventAttribute(name: String) extends OnEventAttribute[SyntheticEvent]

  lazy val onError = OnErrorEventAttribute("onError")
}

trait VirtualDOM extends StaticTags {

  class VirtualDOMElements extends Elements

  object VirtualDOMElements {
    case class ReactClassElementSpec(
        reactClass: ReactClass
    ) {
      def apply(attributes: Any*)(contents: Any*): ReactElement = {
        val element = Element("", attributes, contents)
        React.createElement(
          reactClass,
          VirtualDOMAttributes.toReactAttributes(element.flattenedAttributes),
          toReactElements(element.flattenedContents): _*
        )
      }

      def empty = apply()()
    }

    def toReactElements(contents: Seq[Any]): Seq[js.Any] =
      contents.map(elementToReactElement)

    private def elementToReactElement(content: Any): js.Any =
      content match {
        case element@Element(_, _, _, _) => elementsToVirtualDOMs(element)
        case spec: ReactClassSpec[_, _] => React.createElement(spec)
        case _ => content.asInstanceOf[js.Any]
      }
  }

  class VirtualDOMAttributes extends Attributes
      with EventVirtualDOMAttributes {

    case class RefAttributeSpec(name: String) extends AttributeSpec {
      def :=[T <: ReactHTMLElement](callback: js.Function1[T, _]): Attribute[js.Function1[T, _]] = {
        Attribute(name, callback, AS_IS)
      }
    }

    lazy val className = SpaceSeparatedStringAttributeSpec(name = "className")
    override lazy val `for`: ForAttributeSpec = htmlFor
    lazy val htmlFor = ForAttributeSpec("htmlFor")
    lazy val key = StringAttributeSpec("key")
    lazy val ref = RefAttributeSpec("ref")
  }

  object VirtualDOMAttributes {
    object Type {
      case object AS_IS extends AttributeValueType
    }

    private lazy val htmlNameToReactNameMap = Map(
      "accept-charset" -> "acceptCharset",
      "accesskey" -> "accessKey",
      "allowfullscreen" -> "allowFullScreen",
      "autocomplete" -> "autoComplete",
      "autofocus" -> "autoFocus",
      "autoplay" -> "autoPlay",
      "charset" -> "charSet",
      "colspan" -> "colSpan",
      "contenteditable" -> "contentEditable",
      "contextmenu" -> "contextMenu",
      "crossorigin" -> "crossOrigin",
      "datetime" -> "dateTime",
      "enctype" -> "encType",
      "formaction" -> "formAction",
      "formenctype" -> "formEncType",
      "formmethod" -> "formMethod",
      "formnovalidate" -> "formNoValidate",
      "formtarget" -> "formTarget",
      "hreflang" -> "hrefLang",
      "http-equiv" -> "httpEquiv",
      "inputmode" -> "inputMode",
      "keytype" -> "keyType",
      "maxlength" -> "maxLength",
      "mediagroup" -> "mediaGroup",
      "minlength" -> "minLength",
      "novalidate" -> "noValidate",
      "radiogroup" -> "radioGroup",
      "spellcheck" -> "spellCheck",
      "srcdoc" -> "srcDoc",
      "srclang" -> "srcLang",
      "srcset" -> "srcSet",
      "tabindex" -> "tabIndex",
      "usemap" -> "useMap"
    )

    private def toReactAttributeName(name: String): String = {
      htmlNameToReactNameMap.getOrElse(name, name)
    }

    def toReactAttributes(attributes: Iterable[Attribute[_]]): js.Dictionary[js.Any] =
      attributes
          .map(attributeToReactAttribute)
          .toMap
          .toJSDictionary

    private def attributeToReactAttribute(attribute: Attribute[_]): (String, js.Any) =
      toReactAttributeName(attribute.name) -> attributeValueToReactAttributeValue(attribute)

    private def attributeValueToReactAttributeValue(attribute: Attribute[_]): js.Any =
      attribute match {
        case Attribute(_, value, AttributeValueType.CSS) => value.asInstanceOf[Map[String, _]].toJSDictionary
        case Attribute(_, value: ReactClassSpec[_, _], AS_IS) => React.createClass(value)
        case Attribute(_, value, AS_IS) => value.asInstanceOf[js.Any]
        case _ => attribute.valueToString
      }
  }

  override val < = new VirtualDOMElements()
  override val ^ = new VirtualDOMAttributes()

  implicit class RichElement(element: Element) {
    def asReactElement: ReactElement = {
      elementsToVirtualDOMs(element)
    }
  }

  implicit def elementsToVirtualDOMs(element: Element): ReactElement =
    React.createElement(
      element.name,
      VirtualDOMAttributes.toReactAttributes(element.flattenedAttributes),
      VirtualDOMElements.toReactElements(element.flattenedContents): _*
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy