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

com.raquo.laminar.api.Implicits.scala Maven / Gradle / Ivy

The newest version!
package com.raquo.laminar.api

import com.raquo.airstream.core.{Sink, Source}
import com.raquo.ew
import com.raquo.ew.ewArray
import com.raquo.laminar.api.Implicits.RichSource
import com.raquo.laminar.api.StyleUnitsApi.StyleEncoder
import com.raquo.laminar.inserters.{StaticChildInserter, StaticChildrenInserter, StaticInserter, StaticTextInserter}
import com.raquo.laminar.keys.CompositeKey.CompositeValueMappers
import com.raquo.laminar.keys.{DerivedStyleProp, EventProcessor, EventProp}
import com.raquo.laminar.modifiers.{Binder, Modifier, RenderableNode, RenderableText, Setter}
import com.raquo.laminar.nodes.{ChildNode, ReactiveElement, TextNode}
import org.scalajs.dom

import scala.scalajs.js

trait Implicits extends Implicits.LowPriorityImplicits with CompositeValueMappers {

  /** Add --> methods to Observables */
  @inline implicit def enrichSource[A](source: Source[A]): RichSource[A] = {
    new RichSource(source)
  }


  // -- CSS Styles --

  /** Allow both Int-s and Double-s in numeric style props */
  @inline implicit def derivedStyleIntToDouble(style: DerivedStyleProp[Int]): DerivedStyleProp[Double] = {
    // Safe because Int-s and Double-s have identical runtime representation in Scala.js
    style.asInstanceOf[DerivedStyleProp[Double]]
  }

  /** Allow both Int-s and Double-s in numeric style props */
  @inline implicit def styleEncoderIntToDouble(encoder: StyleEncoder[Int]): StyleEncoder[Double] = {
    // Safe because Int-s and Double-s have identical runtime representation in Scala.js
    encoder.asInstanceOf[StyleEncoder[Double]]
  }


  // -- Basic laminar syntax --

  /** Add [[EventProcessor]] methods (mapToValue / filter / preventDefault / etc.) to event props (e.g. onClick) */
  @inline implicit def eventPropToProcessor[Ev <: dom.Event](eventProp: EventProp[Ev]): EventProcessor[Ev, Ev] = {
    EventProcessor.empty(eventProp)
  }

  /** Convert primitive renderable values (strings, numbers, booleans, etc.) to text nodes */
  implicit def textToTextNode[A](value: A)(implicit r: RenderableText[A]): TextNode = {
    new TextNode(r.asString(value))
  }

  /** Convert a custom component to Laminar DOM node */
  implicit def componentToNode[A](component: A)(implicit r: RenderableNode[A]): ChildNode.Base = {
    r.asNode(component)
  }


  // -- Methods to convert collections of Setter[El] to a single Setter[El] --

  /** Create a [[Setter]] that applies the optionally provided [[Setter]], or else does nothing. */
  implicit def optionToSetter[El <: ReactiveElement.Base](maybeSetter: Option[Setter[El]]): Setter[El] = {
    Setter(element => maybeSetter.foreach(_.apply(element)))
  }

  /** Combine aa Seq of [[Setter]]-s into a single [[Setter]] that applies them all. */
  implicit def seqToSetter[El <: ReactiveElement.Base](setters: collection.Seq[Setter[El]]): Setter[El] = {
    Setter(element => setters.foreach(_.apply(element)))
  }

  /** Combine an Array of [[Setter]]-s into a single [[Setter]] that applies them all. */
  implicit def arrayToSetter[El <: ReactiveElement.Base](setters: scala.Array[Setter[El]]): Setter[El] = {
    Setter(element => setters.foreach(_.apply(element)))
  }

  /** Combine an ew.JsVector of [[Setter]]-s into a single [[Setter]] that applies them all. */
  implicit def jsVectorToSetter[El <: ReactiveElement.Base](setters: ew.JsVector[Setter[El]]): Setter[El] = {
    Setter(element => setters.forEach(_.apply(element)))
  }

  /** Combine an ew.JsArray of [[Setter]]-s into a single [[Setter]] that applies them all. */
  implicit def jsArrayToSetter[El <: ReactiveElement.Base](setters: ew.JsArray[Setter[El]]): Setter[El] = {
    Setter(element => setters.forEach(_.apply(element)))
  }

  /** Combine a js.Array of [[Setter]]-s into a single [[Setter]] that applies them all. */
  implicit def sjsArrayToSetter[El <: ReactiveElement.Base](setters: js.Array[Setter[El]]): Setter[El] = {
    Setter(element => setters.ew.forEach(_.apply(element)))
  }

  /** Create a binder that combines several binders */
  // This can only be implemented with significant caveats, I think. See https://gitter.im/Laminar_/Lobby?at=631a58b4cf6cfd27af7c96b4
  //implicit def seqToBinder[El <: ReactiveElement.Base](binders: collection.Seq[Binder[El]]): Binder[El] = {
  //  Binder[El] { ??? }
  //}


  // -- Methods to convert collections of Modifier[El]-like things to Modifier[El] --

  /** Create a modifier that applies an optional modifier, or does nothing if option is empty */
  implicit def optionToModifier[A, El <: ReactiveElement.Base](
    maybeModifier: Option[A]
  )(
    implicit asModifier: A => Modifier[El]
  ): Modifier[El] = {
    Modifier(element => maybeModifier.foreach(asModifier(_).apply(element)))
  }

  /** Create a modifier that applies each of the modifiers in a seq */
  implicit def seqToModifier[A, El <: ReactiveElement.Base](
    modifiers: collection.Seq[A]
  )(
    implicit asModifier: A => Modifier[El]
  ): Modifier[El] = {
    Modifier(element => modifiers.foreach(asModifier(_).apply(element)))
  }

  /** Create a modifier that applies each of the modifiers in an array */
  implicit def arrayToModifier[A, El <: ReactiveElement.Base](
    modifiers: scala.Array[A]
  )(
    implicit asModifier: A => Modifier[El]
  ): Modifier[El] = {
    Modifier(element => modifiers.foreach(asModifier(_).apply(element)))
  }

  /** Create a modifier that applies each of the modifiers in an array */
  implicit def jsVectorToModifier[A, El <: ReactiveElement.Base](
    modifiers: ew.JsVector[A]
  )(
    implicit asModifier: A => Modifier[El]
  ): Modifier[El] = {
    Modifier(element => modifiers.forEach(asModifier(_).apply(element)))
  }

  /** Create a modifier that applies each of the modifiers in an array */
  implicit def jsArrayToModifier[A, El <: ReactiveElement.Base](
    modifiers: ew.JsArray[A]
  )(
    implicit asModifier: A => Modifier[El]
  ): Modifier[El] = {
    Modifier(element => modifiers.forEach(asModifier(_).apply(element)))
  }

  /** Create a modifier that applies each of the modifiers in an array */
  implicit def sjsArrayToModifier[A, El <: ReactiveElement.Base](
    modifiers: js.Array[A]
  )(
    implicit asModifier: A => Modifier[El]
  ): Modifier[El] = {
    jsArrayToModifier(modifiers.ew)
  }

  // The various collection-to-modifier conversions below are cheaper and better equivalents of
  // collection-to-inserter modifiers found in the `LowPriorityImplicits` trait below.
  // We have a test that will fail should the priority of implicits be wrong.

  // -- Methods to convert collections of nodes to modifiers --

  implicit def nodeOptionToModifier(nodes: Option[ChildNode.Base]): Modifier.Base = {
    Modifier(element => nodes.foreach(_.apply(element)))
  }

  implicit def nodeSeqToModifier(nodes: collection.Seq[ChildNode.Base]): Modifier.Base = {
    Modifier(element => nodes.foreach(_.apply(element)))
  }

  implicit def nodeArrayToModifier[N <: ChildNode.Base](nodes: scala.Array[N]): Modifier.Base = {
    Modifier(element => nodes.foreach(_.apply(element)))
  }

  implicit def nodeJsVectorToModifier[N <: ChildNode.Base](nodes: ew.JsVector[N]): Modifier.Base = {
    Modifier(element => nodes.forEach(_.apply(element)))
  }

  implicit def nodeJsArrayToModifier[N <: ChildNode.Base](nodes: ew.JsArray[N]): Modifier.Base = {
    Modifier(element => nodes.forEach(_.apply(element)))
  }

  implicit def nodeSjsArrayToModifier[N <: ChildNode.Base](nodes: js.Array[N]): Modifier.Base = {
    Modifier(element => nodes.ew.forEach(_.apply(element)))
  }
}

object Implicits {

  /** Some of these methods are redundant, but we need them for type inference to work. */

  class RichSource[A](val source: Source[A]) extends AnyVal {

    def -->(sink: Sink[A]): Binder.Base = {
      Binder(ReactiveElement.bindSink(_, source.toObservable)(sink))
    }

    def -->(onNext: A => Unit): Binder.Base = {
      Binder(ReactiveElement.bindFn(_, source.toObservable)(onNext))
    }

    def -->(onNext: => Unit)(implicit evidence: UnitArrowsFeature): Binder.Base = {
      Binder(ReactiveElement.bindFn(_, source.toObservable)(_ => onNext))
    }

  }


  /** Implicit conversions from X to Inserter are primarily needed for
    * `onMountInsert`, but they are relatively expensive compared to simpler
    * alternatives when a mere Modifier would suffice. And so, the conversions
    * below are de-prioritized.
    */
  trait LowPriorityImplicits {

    // -- Methods to convert individual values / nodes / components to inserters --

    implicit def textToInserter[A](value: A)(implicit r: RenderableText[A]): StaticInserter = {
      if (r == RenderableText.textNodeRenderable) {
        StaticChildInserter.noHooks(value.asInstanceOf[TextNode])
      } else {
        new StaticTextInserter(r.asString(value))
      }
    }

    implicit def nodeToInserter(node: ChildNode.Base): StaticChildInserter = {
      StaticChildInserter.noHooks(node)
    }

    implicit def componentToInserter[Component: RenderableNode](component: Component): StaticChildInserter = {
      StaticChildInserter.noHooksC(component)
    }

    // -- Methods to convert collections of nodes to inserters --

    implicit def nodeOptionToInserter(maybeNode: Option[ChildNode.Base]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooks(maybeNode.toSeq)
    }

    implicit def nodeSeqToInserter(nodes: collection.Seq[ChildNode.Base]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooks(nodes)
    }

    implicit def nodeArrayToInserter(nodes: scala.Array[ChildNode.Base]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooks(nodes)
    }

    implicit def nodeJsVectorToInserter[N <: ChildNode.Base](nodes: ew.JsVector[N]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooks(nodes.toList)
    }

    implicit def nodeJsArrayToInserter[N <: ChildNode.Base](nodes: ew.JsArray[N]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooks(nodes.asScalaJs.toList)
    }

    implicit def nodeSjsArrayToInserter[N <: ChildNode.Base](nodes: js.Array[N]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooks(nodes.toList)
    }

    // -- Methods to convert collections of components to inserters --

    implicit def componentOptionToInserter[Component: RenderableNode](maybeComponent: Option[Component]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooksC(maybeComponent.toList)
    }

    implicit def componentSeqToInserter[Component: RenderableNode](components: collection.Seq[Component]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooksC(components.toList)
    }

    implicit def componentArrayToInserter[Component: RenderableNode](components: scala.Array[Component]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooksC(components.toList)
    }

    implicit def componentJsVectorToInserter[Component: RenderableNode](components: ew.JsVector[Component]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooksC(components.toList)
    }

    implicit def componentJsArrayToInserter[Component: RenderableNode](components: ew.JsArray[Component]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooksC(components.asScalaJs.toList)
    }

    implicit def componentSjsArrayToInserter[Component: RenderableNode](components: js.Array[Component]): StaticChildrenInserter = {
      StaticChildrenInserter.noHooksC(components.toList)
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy