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

outwatch.VMod.scala Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
package outwatch

import outwatch.helpers.NativeHelpers._
import snabbdom.{DataObject, VNodeProxy}
import colibri._
import colibri.effect._
import cats.Monoid
import cats.syntax.functor._
import cats.effect.Sync
import org.scalajs.dom
import outwatch.helpers.ModifierBooleanOps

import scala.scalajs.js
import scala.scalajs.js.JSConverters._

sealed trait VMod {
  @inline final def ++(other: VMod): VMod     = concat(other)
  @inline final def concat(other: VMod): VMod = VMod(this, other)
}

object VMod {
  @inline def empty: VMod = EmptyModifier

  @inline def apply(): VMod = empty

  @inline def apply[T: Render](t: T): VMod = Render[T].render(t)

  @inline def apply(modifier: VMod, modifier2: VMod): VMod =
    CompositeModifier(js.Array(modifier, modifier2))

  @inline def apply(modifier: VMod, modifier2: VMod, modifier3: VMod): VMod =
    CompositeModifier(js.Array(modifier, modifier2, modifier3))
  @inline final def attr[T](key: String, convert: T => Attr.Value = (t: T) => t.toString: Attr.Value) =
    new AttrBuilder.ToBasicAttr[T](key, convert)
  @inline final def prop[T](key: String, convert: T => Prop.Value = (t: T) => t) =
    new AttrBuilder.ToProp[T](key, convert)
  @inline final def style[T](key: String) = new AttrBuilder.ToBasicStyle[T](key)

  def managed[F[_]: Sync: RunEffect, T: CanCancel](subscription: F[T]): VMod = VMod(
    subscription.map[VMod](cancelable => CancelableModifier(() => Cancelable.lift(cancelable))),
  )

  def managedSubscribe[F[_]: Source, T](source: F[T]): VMod = managedEval(
    Source[F].unsafeSubscribe(source)(Observer.empty),
  )

  @deprecated("Use managedEval(subscription) instead", "")
  @inline def managedFunction[T: CanCancel](subscription: () => T): VMod = managedEval(subscription())
  def managedEval[T: CanCancel](subscription: => T): VMod                = CancelableModifier(() => Cancelable.lift(subscription))

  object managedElement {
    def apply[T: CanCancel](subscription: dom.Element => T): VMod = VMod.eval {
      var lastSub: js.UndefOr[T] = js.undefined
      VMod(
        DomMountHook(proxy => proxy.elm.foreach(elm => lastSub = subscription(elm))),
        DomUnmountHook(_ => lastSub.foreach(CanCancel[T].unsafeCancel)),
      )
    }

    @inline def asHtml[T: CanCancel](subscription: dom.html.Element => T): VMod = apply(
      subscription.asInstanceOf[dom.Element => T],
    )

    @inline def asSvg[T: CanCancel](subscription: dom.svg.Element => T): VMod = apply(
      subscription.asInstanceOf[dom.Element => T],
    )
  }

  @inline def apply(modifier: VMod, modifier2: VMod, modifier3: VMod, modifier4: VMod): VMod =
    CompositeModifier(js.Array(modifier, modifier2, modifier3, modifier4))

  @inline def apply(
    modifier: VMod,
    modifier2: VMod,
    modifier3: VMod,
    modifier4: VMod,
    modifier5: VMod,
  ): VMod =
    CompositeModifier(js.Array(modifier, modifier2, modifier3, modifier4, modifier5))

  @inline def apply(
    modifier: VMod,
    modifier2: VMod,
    modifier3: VMod,
    modifier4: VMod,
    modifier5: VMod,
    modifier6: VMod,
  ): VMod =
    CompositeModifier(js.Array(modifier, modifier2, modifier3, modifier4, modifier5, modifier6))

  @inline def apply(
    modifier: VMod,
    modifier2: VMod,
    modifier3: VMod,
    modifier4: VMod,
    modifier5: VMod,
    modifier6: VMod,
    modifier7: VMod,
    modifiers: VMod*,
  ): VMod =
    CompositeModifier(
      js.Array(modifier, modifier2, modifier3, modifier4, modifier5, modifier6, modifier7, CompositeModifier(modifiers)),
    )

  @inline def fromEither[T: Render](modifier: Either[Throwable, T]): VMod = modifier.fold(raiseError(_), apply(_))
  @inline def evalEither[T: Render](modifier: => Either[Throwable, T]): VMod =
    SyncEffectModifier(() => fromEither(modifier))
  @deprecated("Use VMod.eval instead", "1.0.0")
  @inline def delay[T: Render](modifier: => T): VMod     = eval(modifier)
  @inline def eval[T: Render](modifier: => T): VMod      = SyncEffectModifier(() => modifier)
  @inline def composite(modifiers: Iterable[VMod]): VMod = CompositeModifier(modifiers.toJSArray)
  @inline def raiseError[T](error: Throwable): VMod      = ErrorModifier(error)

  @deprecated("Use VMod.when(condition)(...) instead", "")
  @inline def ifTrue(condition: Boolean): ModifierBooleanOps = when(condition)
  @deprecated("Use VMod.whenNot(condition)(...) instead", "")
  @inline def ifNot(condition: Boolean): ModifierBooleanOps   = whenNot(condition)
  @inline def when(condition: Boolean): ModifierBooleanOps    = new ModifierBooleanOps(condition)
  @inline def whenNot(condition: Boolean): ModifierBooleanOps = new ModifierBooleanOps(!condition)

  @inline def toggle(condition: Boolean)(ifTrue: => VMod, ifFalse: => VMod = VMod.empty): VMod =
    if (condition) ifTrue else ifFalse

  implicit object monoid extends Monoid[VMod] {
    @inline def empty: VMod                     = VMod.empty
    @inline def combine(x: VMod, y: VMod): VMod = VMod(x, y)
  }

  implicit object subscriptionOwner extends SubscriptionOwner[VMod] {
    @inline def own(owner: VMod)(subscription: () => Cancelable): VMod =
      VMod(managedEval(subscription()), owner)
  }

  implicit object syncEmbed extends SyncEmbed[VMod] {
    @inline def delay(body: => VMod): VMod = VMod.eval(body)
  }

  @inline implicit def renderToVMod[T: Render](value: T): VMod = Render[T].render(value)
}

sealed trait StaticVMod extends VMod

final case class VNodeProxyNode(proxy: VNodeProxy) extends StaticVMod

final case class Key(value: Key.Value) extends StaticVMod
object Key {
  type Value = DataObject.KeyValue
}

final case class Emitter(eventType: String, trigger: js.Function1[dom.Event, Unit]) extends StaticVMod

sealed trait Attr extends StaticVMod
object Attr {
  type Value = DataObject.AttrValue
}
final case class BasicAttr(title: String, value: Attr.Value)                                                extends Attr
final case class AccumAttr(title: String, value: Attr.Value, accum: (Attr.Value, Attr.Value) => Attr.Value) extends Attr

final case class Prop(title: String, value: Prop.Value) extends StaticVMod
object Prop {
  type Value = DataObject.PropValue
}

sealed trait Style                                                                           extends StaticVMod
final case class AccumStyle(title: String, value: String, accum: (String, String) => String) extends Style
final case class BasicStyle(title: String, value: String)                                    extends Style
final case class DelayedStyle(title: String, value: String)                                  extends Style
final case class RemoveStyle(title: String, value: String)                                   extends Style
final case class DestroyStyle(title: String, value: String)                                  extends Style

sealed trait SnabbdomHook                                                           extends StaticVMod
final case class InitHook(trigger: js.Function1[VNodeProxy, Unit])                  extends SnabbdomHook
final case class InsertHook(trigger: js.Function1[VNodeProxy, Unit])                extends SnabbdomHook
final case class PrePatchHook(trigger: js.Function2[VNodeProxy, VNodeProxy, Unit])  extends SnabbdomHook
final case class UpdateHook(trigger: js.Function2[VNodeProxy, VNodeProxy, Unit])    extends SnabbdomHook
final case class PostPatchHook(trigger: js.Function2[VNodeProxy, VNodeProxy, Unit]) extends SnabbdomHook
final case class DestroyHook(trigger: js.Function1[VNodeProxy, Unit])               extends SnabbdomHook

sealed trait DomHook                                                                   extends StaticVMod
final case class DomMountHook(trigger: js.Function1[VNodeProxy, Unit])                 extends DomHook
final case class DomUnmountHook(trigger: js.Function1[VNodeProxy, Unit])               extends DomHook
final case class DomUpdateHook(trigger: js.Function2[VNodeProxy, VNodeProxy, Unit])    extends DomHook
final case class DomPreUpdateHook(trigger: js.Function2[VNodeProxy, VNodeProxy, Unit]) extends DomHook

final case class NextVMod(modifier: StaticVMod) extends StaticVMod

case object EmptyModifier                                                       extends VMod
final case class CompositeModifier(modifiers: Iterable[VMod])                   extends VMod
final case class StreamModifier(subscription: Observer[VMod] => Cancelable)     extends VMod
final case class ChildCommandsModifier(commands: Observable[Seq[ChildCommand]]) extends VMod
final case class CancelableModifier(subscription: () => Cancelable)             extends VMod
final case class SyncEffectModifier(unsafeRun: () => VMod)                      extends VMod
final case class ErrorModifier(error: Throwable)                                extends VMod
final case class StringVNode(text: String)                                      extends VMod

sealed trait VNode extends VMod {
  def apply(args: VMod*): VNode
  def append(args: VMod*): VNode
  def prepend(args: VMod*): VNode

  @inline final def thunk(key: Key.Value)(arguments: Any*)(renderFn: => VMod): VNode =
    ThunkVNode(this, key, arguments.toJSArray, () => renderFn)
  @inline final def thunkConditional(key: Key.Value)(shouldRender: Boolean)(renderFn: => VMod): VNode =
    ConditionalVNode(this, key, shouldRender, () => renderFn)
  @inline final def thunkStatic(key: Key.Value)(renderFn: => VMod): VNode =
    thunkConditional(key)(false)(renderFn)
}
object VNode {
  private val emptyModifierArray        = js.Array[VMod]()
  @inline def html(name: String): VNode = HtmlVNode(name, emptyModifierArray)
  @inline def svg(name: String): VNode  = SvgVNode(name, emptyModifierArray)

  @inline def raiseError[T](error: Throwable): VNode = dsl.div(VMod.raiseError(error))

  @inline def fromEither(modifier: Either[Throwable, VNode]): VNode    = modifier.fold(raiseError(_), identity)
  @inline def evalEither(modifier: => Either[Throwable, VNode]): VNode = SyncEffectVNode(() => fromEither(modifier))

  @inline def eval(body: => VNode): VNode = SyncEffectVNode(() => body)

  @inline def effect[F[_]: RunSyncEffect](body: F[VNode]): VNode = evalEither(RunSyncEffect[F].unsafeRun(body))

  implicit object subscriptionOwner extends SubscriptionOwner[VNode] {
    @inline def own(owner: VNode)(subscription: () => Cancelable): VNode =
      owner.append(VMod.managedEval(subscription()))
  }

  implicit object syncEmbed extends SyncEmbed[VNode] {
    @inline def delay(body: => VNode): VNode = VNode.eval(body)
  }
}

sealed trait BasicVNode extends VNode {
  def nodeType: String
  def modifiers: js.Array[VMod]
}
@inline final case class ThunkVNode(
  baseNode: VNode,
  key: Key.Value,
  arguments: js.Array[Any],
  renderFn: () => VMod,
) extends VNode {
  @inline def apply(args: VMod*): VNode   = append(args: _*)
  @inline def append(args: VMod*): VNode  = copy(baseNode = baseNode(args: _*))
  @inline def prepend(args: VMod*): VNode = copy(baseNode = baseNode.prepend(args: _*))
}
@inline final case class ConditionalVNode(
  baseNode: VNode,
  key: Key.Value,
  shouldRender: Boolean,
  renderFn: () => VMod,
) extends VNode {
  @inline def apply(args: VMod*): VNode   = append(args: _*)
  @inline def append(args: VMod*): VNode  = copy(baseNode = baseNode(args: _*))
  @inline def prepend(args: VMod*): VNode = copy(baseNode = baseNode.prepend(args: _*))
}
@inline final case class HtmlVNode(nodeType: String, modifiers: js.Array[VMod]) extends BasicVNode {
  @inline def apply(args: VMod*): VNode   = append(args: _*)
  @inline def append(args: VMod*): VNode  = copy(modifiers = appendSeq(modifiers, args))
  @inline def prepend(args: VMod*): VNode = copy(modifiers = prependSeq(modifiers, args))
}
@inline final case class SvgVNode(nodeType: String, modifiers: js.Array[VMod]) extends BasicVNode {
  @inline def apply(args: VMod*): VNode   = append(args: _*)
  @inline def append(args: VMod*): VNode  = copy(modifiers = appendSeq(modifiers, args))
  @inline def prepend(args: VMod*): VNode = copy(modifiers = prependSeq(modifiers, args))
}
@inline final case class SyncEffectVNode(unsafeRun: () => VNode) extends VNode {
  @inline def apply(args: VMod*): VNode   = append(args: _*)
  @inline def append(args: VMod*): VNode  = SyncEffectVNode(() => unsafeRun().append(args))
  @inline def prepend(args: VMod*): VNode = SyncEffectVNode(() => unsafeRun().prepend(args))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy