Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature idea: modifier/node interpolator #401

Open
kubukoz opened this issue Sep 29, 2024 · 7 comments
Open

Feature idea: modifier/node interpolator #401

kubukoz opened this issue Sep 29, 2024 · 7 comments
Assignees
Labels
enhancement New feature or request

Comments

@kubukoz
Copy link
Collaborator

kubukoz commented Sep 29, 2024

Sometimes you just want to do:

div(
  s"today is $myDateSignal and I have $appleCountSignal apples"
)

but what you actually have to do is:

div(
  "today is ", myDateSignal, " and I have ", appleCountSignal, " apples"
)

Alternatively, I suppose you can combine signals:

div(
  (myDateSignal, appleCountSignal).mapN { (date, apples) => 
    s"today is $date and I have $apples apples"
  }
)

but it's still a bit cumbersome, works only with text nodes and will result in the entire text updating, rather than just the small pieces that change.

Perhaps it would make it worth adding an interpolator like nodes"..." which will serve as a Modifier-based Show?

@kubukoz
Copy link
Collaborator Author

kubukoz commented Sep 29, 2024

This seems to be doable in userland so maybe I'll try making it first to see if it's even possible with the current API...

@kubukoz
Copy link
Collaborator Author

kubukoz commented Sep 29, 2024

ok, hear me out:

type IntersperseStrings[T <: Tuple] <: Tuple =
  T match {
    case EmptyTuple => String *: EmptyTuple
    case (t *: ts)  => String *: t *: IntersperseStrings[ts]
  }

inline def intersperseStrings[T <: Tuple](t: T, strings: Seq[String]): IntersperseStrings[T] =
  inline scala.compiletime.erasedValue[T] match {
    case _: EmptyTuple => strings.head *: EmptyTuple
    case _: (head *: tail) =>
      inline t match {
        case v: (`head` *: `tail`) =>
          val (h *: t) = v
          strings.head *: h *: intersperseStrings(t, strings.tail)
      }
  }

extension (sc: StringContext) {

  inline def nodes[M <: Tuple, E <: HtmlElement[IO]](
    arg: M
  )(
    using Modifier[IO, E, M]
  ): IntersperseStrings[M] = {
    StringContext.checkLengths(arg.toList, sc.parts)
    intersperseStrings(arg, sc.parts.map(StringContext.processEscapes))
  }

}


  def go(myDateSignal: Signal[IO, String], appleCountSignal: Signal[IO, String]) = div(
    nodes"today is $myDateSignal and I have $appleCountSignal apples, let me say that again $appleCountSignal apples"
  )


  def go2(myDateSignal: Signal[IO, String], appleCountSignal: Signal[IO, String]) = div(
    nodes"today is $myDateSignal and I have $appleCountSignal apples"
  )

Probably missing some edge cases.

@kubukoz
Copy link
Collaborator Author

kubukoz commented Sep 29, 2024

Need a special case for a no-tuple scenario. Also, it doesn't work if you actually try to interpolate a tuple :/

nodes"today is ${(myDateSignal, "foo")}"

The interpolator will see this as one tuple of length 2, and there are 2 parts in the StringContext - the length check will fail.

@armanbilge
Copy link
Owner

I like this idea — similar to what Svelte can do.

@ansh7432
Copy link
Contributor

ansh7432 commented Mar 6, 2025

Can I work on this issue @kubukoz @armanbilge likes the idea please assign ☺️

@kubukoz
Copy link
Collaborator Author

kubukoz commented Mar 6, 2025

Sure, go ahead. I think you can work off of my last big snippet...

I don't have an obvious solution for this problem, but it probably just requires some focus.

@ansh7432
Copy link
Contributor

ansh7432 commented Mar 8, 2025

Yeah @kubukoz looking for it 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants