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

japgolly.scalajs.react.Ref.scala Maven / Gradle / Ivy

The newest version!
package japgolly.scalajs.react

import japgolly.scalajs.react.util.DefaultEffects
import japgolly.scalajs.react.util.Effect._
import japgolly.scalajs.react.util.JsUtil.jsNullToOption
import japgolly.scalajs.react.util.Util.identityFn
import japgolly.scalajs.react.vdom.TopNode
import scala.reflect.ClassTag
import scala.scalajs.js
import scala.scalajs.js.|

/** Types:
  *
  *   - `Handle` - `raw: facade.React.RefHandle[A | Null]`
  *   - `Get`    - `get: F[Option[A]]`
  *   - `Set`    - `set: Option[A] => F[Unit]`
  *   - `Full`   - `Handle & Set & Get`
  *   - `Simple` - Monomorphic version of `Full`
  */
object Ref {
  import japgolly.scalajs.react.component.{Js => JsComponent, Scala => ScalaComponent}

  def apply[A]: Simple[A] =
    fromJs(facade.React.createRef[A | Null]())

  def fromJs[A](raw: facade.React.RefHandle[A | Null]): Simple[A] =
    Full(raw, identityFn, Some(_))

  def forwardedFromJs[A](f: facade.React.ForwardedRef[A]): Option[Simple[A]] =
    jsNullToOption(f).map(fromJs)

  type Full   [I, A, O] = FullF  [DefaultEffects.Sync, I, A, O]
  type Get    [A]       = GetF   [DefaultEffects.Sync, A]
  type Handle [A]       = HandleF[DefaultEffects.Sync, A]
  type Set    [A]       = SetF   [DefaultEffects.Sync, A]
  type Simple [A]       = SimpleF[DefaultEffects.Sync, A]
  type SimpleF[F[_], A] = FullF  [F, A, A, A]

  trait HandleF[F[_], A] {
    val raw: facade.React.RefHandle[A | Null]
    final def root: Simple[A] = fromJs(raw)
  }

  trait GetF[F[_], A] { self =>
    protected[Ref] def F: Sync[F]
    def withEffect[G[_]](implicit G: Sync[G]): GetF[G, A]

    def get: F[Option[A]]

    def map[B](f: A => B): GetF[F, B]

    def mapOption[B](f: A => Option[B]): GetF[F, B]

    def narrowOption[B <: A](implicit ct: ClassTag[B]): GetF[F, B]

    def widen[B >: A]: GetF[F, B]

    final def foreach(f: A => Unit): F[Unit] =
      foreachCB(a => F.delay(f(a)))

    final def foreachCB(f: A => F[Unit]): F[Unit] =
      F.flatMap(get) {
        case Some(a) => f(a)
        case None    => F.empty
      }

    /** Get the reference immediately.
      *
      * ONLY USE THIS IN UNIT TESTS. DO NOT USE THIS IN PRODUCTION CODE.
      *
      * Unsafe for two reasons:
      *
      * 1. It reads an underlying variable. (impurity)
      * 2. It throws an exception when the ref is empty (partiality)
      */
    final def unsafeGet(): A =
      F.runSync(get).getOrElse(sys error "Reference is empty")
  }

  trait SetF[F[_], A] {
    protected[Ref] def F: Sync[F]
    def withEffect[G[_]](implicit G: Sync[G]): SetF[G, A]

    /** NOTE: This doesn't force an update-to/redraw-of your component. */
    def set(newValue: Option[A]): F[Unit]

    final lazy val rawSetFn: facade.React.RefFn[A] =
      (n: A | Null) => F.runSync(set(jsNullToOption(n)))

    def contramap[B](f: B => A): SetF[F, B]

    def narrow[B <: A]: SetF[F, B]
  }

  trait FullF[F[_], I, A, O] extends HandleF[F, A] with SetF[F, I] with GetF[F, O] { self =>
    override def withEffect[G[_]](implicit G: Sync[G]): FullF[G, I, A, O]
    override def contramap[X](f: X => I): FullF[F, X, A, O]
    override def narrow[X <: I]: FullF[F, X, A, O]
    override def map[X](f: O => X): FullF[F, I, A, X]
    override def widen[X >: O]: FullF[F, I, A, X]
    override def mapOption[B](f: O => Option[B]): FullF[F, I, A, B]

    final override def narrowOption[B <: O](implicit ct: ClassTag[B]): FullF[F, I, A, B] =
      mapOption(ct.unapply)

    /** NOTE: This doesn't force an update-to/redraw-of your component. */
    final def mod(f: Option[O] => Option[I]): F[Unit] =
      F.flatMap(get)(o => set(f(o)))
  }

  def Full[I, A, O](raw: facade.React.RefHandle[A | Null], l: I => A, r: A => Option[O]): Full[I, A, O] =
    FullF(raw, l, r)(DefaultEffects.Sync)

  def FullF[F[_], I, A, O](_raw: facade.React.RefHandle[A | Null], l: I => A, r: A => Option[O])(implicit FF: Sync[F]): FullF[F, I, A, O] =
    new FullF[F, I, A, O] {

      override protected[Ref] def F = FF

      override def withEffect[G[_]](implicit G: Sync[G]) =
        G.subst[F, ({type L[E[_]] = FullF[E, I, A, O]})#L](this)(
          FullF(raw, l, r)(G))

      override val raw = _raw

      override def set(newValue: Option[I]) =
        F.delay {
          raw.current = newValue match {
            case Some(i) => l(i)
            case None    => null
          }
        }

      override val get =
        F.delay(jsNullToOption(raw.current).flatMap(r))

      override def contramap[X](f: X => I): FullF[F, X, A, O] =
        FullF(raw, l compose f, r)

      override def narrow[X <: I]: FullF[F, X, A, O] =
        FullF(raw, l, r)

      override def map[X](f: O => X): FullF[F, I, A, X] =
        FullF(raw, l, r.andThen(_.map(f)))

      override def widen[X >: O]: FullF[F, I, A, X] =
        FullF(raw, l, r)

      override def mapOption[B](f: O => Option[B]): FullF[F, I, A, B] =
        FullF(raw, l, r.andThen(_.flatMap(f)))
    }

  // ===================================================================================================================

  type ToComponent[I, R, O, C] = ToComponentF[DefaultEffects.Sync, I, R, O, C]

  final class ToComponentF[F[_], I, R, O, C](ref: FullF[F, I, R, O], val component: C) extends FullF[F, I, R, O] {
    override protected[Ref] def F = ref.F

    override def withEffect[G[_]](implicit G: Sync[G]) =
      G.subst[F, ({type L[E[_]] = ToComponentF[E, I, R, O, C]})#L](this)(
        new ToComponentF(ref.withEffect[G], component)
      )(F)

    override val raw = ref.raw
    override val get = ref.get
    override def set(o: Option[I]) = ref.set(o)

    override def contramap[A](f: A => I): ToComponentF[F, A, R, O, C] =
      ToComponent(ref.contramap(f), component)

    override def map[A](f: O => A): ToComponentF[F, I, R, A, C] =
      ToComponent(ref.map(f), component)

    override def mapOption[B](f: O => Option[B]): FullF[F, I, R, B] =
      ToComponent(ref.mapOption(f), component)

    override def widen[A >: O]: ToComponentF[F, I, R, A, C] =
      map[A](o => o)

    override def narrow[A <: I]: ToComponentF[F, A, R, O, C] =
      contramap[A](a => a)
  }

  object ToComponent {

    def apply[F[_], I, R, O, C](ref: FullF[F, I, R, O], c: C): ToComponentF[F, I, R, O, C] =
      new ToComponentF(ref, c)

    def inject[F[_], I, R, O, CT[-p, +u] <: CtorType[p, u], P, U](c: CT[P, U], ref: FullF[F, I, R, O]): ToComponentF[F, I, R, O, CT[P, U]] =
      apply(ref, CtorType.hackBackToSelf[CT, P, U](c)(c.withRawProp("ref", ref.rawSetFn)))
  }

  // ===================================================================================================================

  type ToJsComponentF[F[_], P <: js.Object, S <: js.Object, R <: JsComponent.RawMounted[P, S]] =
    FullF[F, R, R, JsComponent.MountedWithRawType[P, S, R]]

  type ToJsComponent[P <: js.Object, S <: js.Object, R <: JsComponent.RawMounted[P, S]] =
    ToJsComponentF[DefaultEffects.Sync, P, S, R]

  def toJsComponent[P <: js.Object, S <: js.Object]: ToJsComponent[P, S, JsComponent.RawMounted[P, S]] =
    apply[JsComponent.RawMounted[P, S]].map(JsComponent.mounted[P, S](_))

  def toJsComponentWithMountedFacade[P <: js.Object, S <: js.Object, F <: js.Object]: ToJsComponent[P, S, JsComponent.RawMounted[P, S] with F] =
    apply[JsComponent.RawMounted[P, S] with F].map(JsComponent.mounted[P, S](_).addFacade[F])

  type WithJsComponentF[FR[_], FJ[_], A[_], P1, S1, CT1[-p, +u] <: CtorType[p, u], R <: JsComponent.RawMounted[P0, S0], P0 <: js.Object, S0 <: js.Object] =
    ToComponentF[FR, R, R, JsComponent.MountedWithRawType[P0, S0, R],
      CT1[P1, JsComponent.UnmountedMapped[FJ, A, P1, S1, R, P0, S0]]]

  type WithJsComponent[F[_], A[_], P1, S1, CT1[-p, +u] <: CtorType[p, u], R <: JsComponent.RawMounted[P0, S0], P0 <: js.Object, S0 <: js.Object] =
    WithJsComponentF[DefaultEffects.Sync, F, A, P1, S1, CT1, R, P0, S0]

  def toJsComponent[F[_], A[_], P1, S1, CT1[-p, +u] <: CtorType[p, u], R <: JsComponent.RawMounted[P0, S0], P0 <: js.Object, S0 <: js.Object, CT0[-p, +u] <: CtorType[p, u]]
      (a: WithJsComponentArg[F, A, P1, S1, CT1, R, P0, S0])
      : WithJsComponent[F, A, P1, S1, CT1, R, P0, S0] =
    a.wrap(toJsComponentWithMountedFacade[P0, S0, R])

  // Ridiculous that this is needed but Scala needs explicit help when F=Effect.Id
  final class WithJsComponentArg[F[_], A[_], P1, S1, CT1[-p, +u] <: CtorType[p, u], R <: JsComponent.RawMounted[P0, S0], P0 <: js.Object, S0 <: js.Object]
      (val wrap: ToJsComponent[P0, S0, JsComponent.RawMounted[P0, S0] with R] =>
                 WithJsComponent[F, A, P1, S1, CT1, R, P0, S0]
      ) extends AnyVal

  object WithJsComponentArg {
    implicit def direct[F[_], A[_], P1, S1, CT1[-p, +u] <: CtorType[p, u], R <: JsComponent.RawMounted[P0, S0], P0 <: js.Object, S0 <: js.Object, CT0[-p, +u] <: CtorType[p, u]]
        (c: JsComponent.ComponentMapped[F, A, P1, S1, CT1, R, P0, S0, CT0])
        : WithJsComponentArg[F, A, P1, S1, CT1, R, P0, S0] =
      new WithJsComponentArg[F, A, P1, S1, CT1, R, P0, S0](ToComponent.inject(c, _))

    implicit def effectId[A[_], P1, S1, CT1[-p, +u] <: CtorType[p, u], R <: JsComponent.RawMounted[P0, S0], P0 <: js.Object, S0 <: js.Object, CT0[-p, +u] <: CtorType[p, u]]
        (c: JsComponent.ComponentMapped[Id, A, P1, S1, CT1, R, P0, S0, CT0])
        : WithJsComponentArg[Id, A, P1, S1, CT1, R, P0, S0] =
      new WithJsComponentArg[Id, A, P1, S1, CT1, R, P0, S0](ToComponent.inject(c, _))
  }

  // ===================================================================================================================

  type ToScalaComponentF[F[_], P, S, B] =
    FullF[
      F,
      ScalaComponent.RawMounted[P, S, B],
      ScalaComponent.RawMounted[P, S, B],
      ScalaComponent.MountedImpure[P, S, B]]

  type ToScalaComponent[P, S, B] =
    ToScalaComponentF[DefaultEffects.Sync, P, S, B]

  def toScalaComponent[P, S, B]: ToScalaComponent[P, S, B] =
    apply[ScalaComponent.RawMounted[P, S, B]].map(_.mountedImpure)

  type WithScalaComponentF[F[_], P, S, B, CT[-p, +u] <: CtorType[p, u]] =
    ToComponentF[
      F,
      ScalaComponent.RawMounted[P, S, B],
      ScalaComponent.RawMounted[P, S, B],
      ScalaComponent.MountedImpure[P, S, B],
      CT[P, ScalaComponent.Unmounted[P, S, B]]]

  type WithScalaComponent[P, S, B, CT[-p, +u] <: CtorType[p, u]] =
    WithScalaComponentF[DefaultEffects.Sync, P, S, B, CT]

  def toScalaComponent[P, S, B, CT[-p, +u] <: CtorType[p, u]]
                      (c: ScalaComponent.Component[P, S, B, CT])
                      : WithScalaComponent[P, S, B, CT] =
    ToComponent.inject(c, toScalaComponent[P, S, B])

  // ===================================================================================================================

  type ToAnyVdom = Simple[TopNode]

  /** For use with the `untypedRef` vdom attribute. */
  def toAnyVdom(): ToAnyVdom =
    apply

  type ToVdom[N <: TopNode] = Full[TopNode, TopNode, N]

  /** For use with the `untypedRef` vdom attribute. */
  def toVdom[N <: TopNode : ClassTag]: ToVdom[N] =
    toAnyVdom().narrowOption[N]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy