Skip to content

Commit e9a0525

Browse files
committed
Merge commit '3fbc61b3ec6cdc58e7f2b7b1c3a716f18d16abc8' into update/template
2 parents 6a6c858 + 3fbc61b commit e9a0525

File tree

20 files changed

+1153
-2
lines changed

20 files changed

+1153
-2
lines changed

.git-blame-ignore-revs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# Scala Steward: Reformat with scalafmt 3.1.2
22
fb6cfb8aea15a1b339e3ed69e1e96acd7df4cae6
3+
4+
# Scala Steward: Reformat with scalafmt 3.7.2
5+
127db711dde71e3e028549511b33f39da85ed9a8

.github/workflows/scala.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
scala:
19-
- 2.12.20
18+
include:
19+
- scala: 2.12.13
20+
sbt-args: --addPluginSbtFile=project/plugins.sbt.scalajs06
21+
- scala: 2.13.4
22+
sbt-args: --addPluginSbtFile=project/plugins.sbt.scalajs06
23+
- scala: 2.13.10
24+
- scala: 3.2.2
2025

2126
steps:
2227
- uses: actions/checkout@v3

.node-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
16.13.0

BindingHtmlToReact/build.sbt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
enablePlugins(ScalaJSPlugin)
2+
3+
enablePlugins(ScalaJSBundlerPlugin)
4+
5+
enablePlugins(Example)
6+
7+
libraryDependencies += {
8+
if (VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector(">=1"))) {
9+
"me.shadaj" %%% "slinky-web" % "0.7.3"
10+
} else {
11+
"me.shadaj" %%% "slinky-web" % "0.6.8"
12+
}
13+
}
14+
15+
libraryDependencies += {
16+
if (
17+
VersionNumber(scalaVersion.value).matchesSemVer(SemanticSelector(">=2.13"))
18+
) {
19+
"com.thoughtworks.binding" %%% "bindable" % "3.0.0"
20+
} else {
21+
"com.thoughtworks.binding" %%% "bindable" % "1.1.0"
22+
}
23+
}
24+
25+
libraryDependencies += {
26+
if (
27+
VersionNumber(scalaVersion.value).matchesSemVer(SemanticSelector(">=3"))
28+
) {
29+
"com.yang-bo" %%% "html" % "3.0.2" % Optional
30+
} else {
31+
"com.yang-bo" %%% "html" % "2.0.2" % Optional
32+
}
33+
}
34+
35+
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.15" % Test
36+
37+
libraryDependencies += {
38+
if (VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector(">=1"))) {
39+
"me.shadaj" %%% "slinky-testrenderer" % "0.7.3" % Test
40+
} else {
41+
"me.shadaj" %%% "slinky-testrenderer" % "0.6.8" % Test
42+
}
43+
}
44+
45+
Test / requireJsDomEnv := true
46+
47+
Test / npmDependencies += "react-dom" -> "18.2.0"
48+
49+
Test / npmDependencies += "react-test-renderer" -> "18.2.0"
50+
51+
scalacOptions ++= PartialFunction.condOpt(
52+
VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector("<1"))
53+
) { case true =>
54+
"-P:scalajs:sjsDefinedByDefault"
55+
}
56+
57+
scalacOptions ++= PartialFunction.condOpt(
58+
scalaBinaryVersion.value
59+
) { case "2.13" =>
60+
"-Ymacro-annotations"
61+
}
62+
63+
libraryDependencies ++= PartialFunction.condOpt(
64+
scalaBinaryVersion.value
65+
) { case "2.12" =>
66+
compilerPlugin(
67+
"org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full
68+
)
69+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.yang_bo
2+
3+
import com.thoughtworks.binding.Binding.BindingSeq
4+
import org.scalajs.dom._
5+
import org.scalajs.dom.raw._
6+
import slinky.core.facade._
7+
8+
private[yang_bo] trait BindingHtmlToReactImplicits2Or3 {
9+
10+
/** Implicitly convents an [[https://github.com/Atry/html.scala html.scala]]
11+
* `@html` literal into a React component.
12+
* @example
13+
* The following code creates a spinner from the `@html` literal:
14+
* {{{
15+
* import com.thoughtworks.binding.Binding
16+
* import com.thoughtworks.binding.Binding.Var
17+
* import org.lrng.binding.html
18+
* import org.scalajs.dom._
19+
* import org.scalajs.dom.raw._
20+
*
21+
* @html def spinner(currentNumber: Var[Int]) = {
22+
* <button
23+
* id="minus"
24+
* onclick={ (e: Event) => currentNumber.value -= 1 }
25+
* >-</button>
26+
* <label>{currentNumber.bind.toString}</label>
27+
* <button
28+
* id="plus"
29+
* onclick={ (e: Event) => currentNumber.value += 1 }
30+
* >+</button>
31+
* }
32+
* }}}
33+
* The `@html` literal can be then used as a React component with the help
34+
* of [[BindingHtmlToReact.Implicits]]:
35+
* {{{
36+
* import com.yang_bo.BindingHtmlToReact.Implicits._
37+
* import slinky.web.html._
38+
* val currentNumber = Var(50)
39+
* def reactRoot = fieldset(
40+
* legend("I am a React component that contains an `@html` literal"),
41+
* spinner(currentNumber)
42+
* )
43+
* }}}
44+
* Then, the `@html` literal can be
45+
* [[org.lrng.binding.html.render render]]ed into the html document,
46+
* {{{
47+
* import slinky.web.ReactDOM
48+
* import slinky.testrenderer.TestRenderer
49+
* import org.scalajs.dom.document
50+
* TestRenderer.act { () =>
51+
* ReactDOM.render(reactRoot, document.body)
52+
* }
53+
* currentNumber.value should be(50)
54+
* document.body.innerHTML should be(
55+
* """<fieldset><legend>I am a React component that contains an `@html` literal</legend><span><button id="minus">-</button><label>50</label><button id="plus">+</button></span></fieldset>"""
56+
* )
57+
* }}}
58+
* and it can respond to UI events
59+
* {{{
60+
* document
61+
* .getElementById("minus")
62+
* .asInstanceOf[HTMLButtonElement]
63+
* .onclick(
64+
* new MouseEvent("click", new MouseEventInit {
65+
* bubbles = true
66+
* cancelable = true
67+
* })
68+
* )
69+
* currentNumber.value should be(49)
70+
* document.body.innerHTML should be(
71+
* """<fieldset><legend>I am a React component that contains an `@html` literal</legend><span><button id="minus">-</button><label>49</label><button id="plus">+</button></span></fieldset>"""
72+
* )
73+
* }}}
74+
*/
75+
def bindingSeqToReactElement(
76+
bindingSeq: BindingSeq[Node]
77+
): ReactElement
78+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.yang_bo
2+
3+
import com.thoughtworks.binding.Binding.BindingSeq
4+
import org.scalajs.dom.*
5+
import slinky.core.facade.*
6+
7+
private[yang_bo] trait BindingHtmlToReactImplicits2Or3 {
8+
9+
/** Implicitly convents an `html"..."` interpolation into a React component.
10+
* @example
11+
* The following code creates a spinner from the `html"..."` interpolation
12+
* provided by [[com.yang_bo.html]]:
13+
* {{{
14+
* import com.thoughtworks.binding.Binding
15+
* import com.thoughtworks.binding.Binding.Var
16+
* import com.yang_bo.html.*
17+
* import org.scalajs.dom.*
18+
*
19+
* def spinner(currentNumber: Var[Int]) = html"""
20+
* <button
21+
* id="minus"
22+
* onclick=${ (e: Event) => currentNumber.value -= 1 }
23+
* >-</button>
24+
* <label>${currentNumber.bind.toString}</label>
25+
* <button
26+
* id="plus"
27+
* onclick=${ (e: Event) => currentNumber.value += 1 }
28+
* >+</button>
29+
* """
30+
* }}}
31+
* The `html"..."` interpolation can be then used as a React component with
32+
* the help of [[BindingHtmlToReact.Implicits]]:
33+
* {{{
34+
* import com.yang_bo.BindingHtmlToReact.Implicits.*
35+
* import slinky.web.html.*
36+
* val currentNumber = Var(50)
37+
* def reactRoot = fieldset(
38+
* legend("I am a React component that contains an html interpolation"),
39+
* spinner(currentNumber)
40+
* )
41+
* }}}
42+
* Then, the `html"..."` interpolation can be
43+
* [[com.yang_bo.html.render render]]ed into the html document,
44+
* {{{
45+
* import slinky.web.ReactDOM
46+
* import slinky.testrenderer.TestRenderer
47+
* import org.scalajs.dom.document
48+
* TestRenderer.act { () =>
49+
* ReactDOM.render(reactRoot, document.body)
50+
* }
51+
* currentNumber.value should be(50)
52+
* document.body.innerHTML should be(
53+
* """<fieldset><legend>I am a React component that contains an html interpolation</legend><span>
54+
* <button id="minus">-</button>
55+
* <label>50</label>
56+
* <button id="plus">+</button>
57+
* </span></fieldset>"""
58+
* )
59+
* }}}
60+
* and, the `html"..."` interpolation can respond to UI events
61+
* {{{
62+
* document
63+
* .getElementById("minus")
64+
* .asInstanceOf[HTMLButtonElement]
65+
* .onclick(
66+
* new MouseEvent("click", new MouseEventInit {
67+
* bubbles = true
68+
* cancelable = true
69+
* })
70+
* )
71+
* currentNumber.value should be(49)
72+
* document.body.innerHTML should be(
73+
* """<fieldset><legend>I am a React component that contains an html interpolation</legend><span>
74+
* <button id="minus">-</button>
75+
* <label>49</label>
76+
* <button id="plus">+</button>
77+
* </span></fieldset>"""
78+
* )
79+
* }}}
80+
*/
81+
def bindingSeqToReactElement(
82+
bindingSeq: BindingSeq[Node]
83+
): ReactElement
84+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.yang_bo
2+
3+
import com.thoughtworks.binding.Binding
4+
import com.thoughtworks.binding.Binding.Var
5+
import com.thoughtworks.binding.Binding.BindingSeq
6+
import com.thoughtworks.binding.bindable.Bindable
7+
import com.thoughtworks.binding.bindable.BindableSeq
8+
import org.scalajs.dom._
9+
import org.scalajs.dom.raw._
10+
import slinky.core._
11+
import slinky.core.facade._
12+
import slinky.web.ReactDOM
13+
import slinky.web.html._
14+
15+
import scala.language.implicitConversions
16+
import scala.scalajs.js
17+
import scala.annotation.tailrec
18+
19+
/** A React component that wraps an
20+
* [[https://github.com/Atry/html.scala html.scala]] node sequence.
21+
* @see
22+
* [[Implicits.bindingSeqToReactElement]] for inserting an `@html` literal or
23+
* `html"..."` interpolation into React virtual DOM.
24+
*/
25+
object BindingHtmlToReact extends ComponentWrapper {
26+
final case class Props(
27+
bindingSeq: BindingSeq[Node],
28+
wrapper: ReactRef[Node with ParentNode] => ReactElement
29+
)
30+
type State = Unit
31+
32+
final class Def(jsProps: js.Object) extends Definition(jsProps) with js.Any {
33+
private val wrapperVar = Var[Option[Node with ParentNode]](None)
34+
private val mountPoint = Binding {
35+
wrapperVar.bind match {
36+
case Some(wrapperElement) =>
37+
new HtmlMountPoint(wrapperElement, props.bindingSeq).bind
38+
case None =>
39+
}
40+
}
41+
42+
def setter: js.Function1[Element, Unit] = { wrapperElement =>
43+
wrapperVar.value = Some(wrapperElement)
44+
}
45+
46+
def initialState = ()
47+
48+
def render() =
49+
props.wrapper(setter.asInstanceOf[ReactRef[Node with ParentNode]])
50+
51+
override def componentDidMount(): Unit = {
52+
super.componentDidMount()
53+
mountPoint.watch()
54+
}
55+
56+
override def componentWillUnmount(): Unit = {
57+
mountPoint.unwatch()
58+
super.componentWillUnmount()
59+
}
60+
61+
}
62+
63+
private[BindingHtmlToReact] trait LowPriorityImplicits1024 {
64+
@inline implicit def bindableSeqToReactElement[From](
65+
from: From
66+
)(implicit bindableSeq: BindableSeq.Lt[From, Node]): ReactElement =
67+
BindingHtmlToReact(
68+
Props(
69+
bindableSeq.toBindingSeq(from),
70+
wrapperRef => span(ref := wrapperRef)
71+
)
72+
)
73+
}
74+
75+
object Implicits
76+
extends LowPriorityImplicits1024
77+
with BindingHtmlToReactImplicits2Or3 {
78+
79+
@inline implicit def bindingSeqToReactElement(
80+
bindingSeq: BindingSeq[Node]
81+
): ReactElement =
82+
BindingHtmlToReact(
83+
Props(bindingSeq, wrapperRef => span(ref := wrapperRef))
84+
)
85+
}
86+
}

BindingReactToReact/build.sbt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
enablePlugins(ScalaJSPlugin)
2+
3+
enablePlugins(ScalaJSBundlerPlugin)
4+
5+
enablePlugins(Example)
6+
7+
libraryDependencies += {
8+
if (VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector(">=1"))) {
9+
"me.shadaj" %%% "slinky-core" % "0.7.3"
10+
} else {
11+
"me.shadaj" %%% "slinky-core" % "0.6.8"
12+
}
13+
}
14+
15+
libraryDependencies += {
16+
if (
17+
VersionNumber(scalaVersion.value).matchesSemVer(SemanticSelector(">=2.13"))
18+
) {
19+
"com.thoughtworks.binding" %%% "binding" % "12.2.0"
20+
} else {
21+
"com.thoughtworks.binding" %%% "binding" % "11.9.0"
22+
}
23+
}
24+
25+
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.15" % Test
26+
27+
libraryDependencies += {
28+
if (VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector(">=1"))) {
29+
"me.shadaj" %%% "slinky-testrenderer" % "0.7.3" % Test
30+
} else {
31+
"me.shadaj" %%% "slinky-testrenderer" % "0.6.8" % Test
32+
}
33+
}
34+
35+
libraryDependencies += {
36+
if (VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector(">=1"))) {
37+
"me.shadaj" %%% "slinky-web" % "0.7.3" % Test
38+
} else {
39+
"me.shadaj" %%% "slinky-web" % "0.6.8" % Test
40+
}
41+
}
42+
43+
Test / requireJsDomEnv := true
44+
45+
Test / npmDependencies += "react-dom" -> "18.2.0"
46+
47+
Test / npmDependencies += "react-test-renderer" -> "18.2.0"
48+
49+
scalacOptions ++= PartialFunction.condOpt(
50+
VersionNumber(scalaJSVersion).matchesSemVer(SemanticSelector("<1"))
51+
) { case true =>
52+
"-P:scalajs:sjsDefinedByDefault"
53+
}

0 commit comments

Comments
 (0)