Time

Basic Interval Stream

import calico.*
import calico.html.io.{*, given}
import calico.syntax.*
import calico.unsafe.given
import cats.effect.*
import cats.effect.std.Random
import cats.effect.syntax.all.*
import cats.syntax.all.*
import fs2.*
import fs2.concurrent.*

import scala.concurrent.duration.*

val app = Stream.fixedRate[IO](1.second).as(1).scanMonoid.holdOptionResource
  .flatMap { tick =>
    div(
      div("Tick #: ", tick.map(_.toString)),
      div(
        "Random #: ",
        Random.scalaUtilRandom[IO].toResource.flatMap { random =>
          tick.discrete
            .evalMap(_ => random.nextInt.map(i => (i % 100).toString))
            .holdOptionResource
        }
      )
    )
  }

app.renderInto(node.asInstanceOf[fs2.dom.Node[IO]]).allocated.unsafeRunAndForget()

Delay

import calico.*
import calico.html.io.{*, given}
import calico.syntax.*
import calico.unsafe.given
import cats.effect.*
import cats.effect.syntax.all.*
import cats.syntax.all.*
import fs2.*
import fs2.concurrent.*

import scala.concurrent.duration.*

val app = Channel.unbounded[IO, Unit].toResource.flatMap { clickCh =>
  val alert = clickCh.stream >>
    (Stream.emit("Just clicked!") ++ Stream.sleep_[IO](500.millis) ++ Stream.emit(""))

  div(
    button(onClick --> (_.void.through(clickCh.sendAll)), "Click me"),
    alert.holdResource("")
  )
}

app.renderInto(node.asInstanceOf[fs2.dom.Node[IO]]).allocated.unsafeRunAndForget()

Debounce

import calico.*
import calico.html.io.{*, given}
import calico.syntax.*
import calico.unsafe.given
import cats.data.*
import cats.effect.*
import cats.effect.syntax.all.*
import cats.syntax.all.*
import fs2.*
import fs2.concurrent.*

import scala.concurrent.duration.given

def validateEmail(email: String): Either[String, Unit] =
  if email.isEmpty then Left("Please fill out email")
  else if !email.contains('@') then Left("Invalid email!")
  else Right(())

val app = Channel.unbounded[IO, String].toResource.flatMap { emailCh =>
  val validated = emailCh.stream.debounce(1.second).map(validateEmail)
  validated.holdOptionResource.flatMap { validatedSig =>
    div(
      span(
        label("Your email: "),
        input.withSelf { self =>
          onInput --> (_.evalMap(_ => self.value.get).through(emailCh.sendAll))
        }
      ),
      span(
        cls <-- Nested(validatedSig).map {
          case Left(_) => List("-error")
          case Right(_) => List("-success")
        }.value,
        Nested(validatedSig).map {
          case Left(err) => s"Error: $err"
          case Right(()) => "Email ok!"
        }.value
      )
    )
  }
}

app.renderInto(node.asInstanceOf[fs2.dom.Node[IO]]).allocated.unsafeRunAndForget()