Skip to content

Commit

Permalink
RBuilder, RDOMBuilder, RElementBuilder, StyledElementBuilder, StyledD… (
Browse files Browse the repository at this point in the history
#448)

* RBuilder, RDOMBuilder, RElementBuilder, StyledElementBuilder, StyledDOMBuilder -> interfaces

* remove commented lines

* add child import to DSL.kt
  • Loading branch information
Skolotsky authored May 21, 2021
1 parent b0ea690 commit 4115486
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 92 deletions.
122 changes: 65 additions & 57 deletions kotlin-react-dom/src/main/kotlin/react/dom/RDOMBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,72 @@ external interface DOMProps : WithClassName {
}

@ReactDsl
open class RDOMBuilder<out T : Tag>(factory: (TagConsumer<Unit>) -> T) : RBuilder() {
interface RDOMBuilder<out T : Tag>: RBuilder {
fun setProp(attribute: String, value: Any?) {
val key = fixAttributeName(attribute)
props.asDynamic()[key] = value
domProps.asDynamic()[key] = value
}

val consumer = object : TagConsumer<Unit> {
val consumer: TagConsumer<Unit>
val attrs: T

operator fun Tag.get(name: String) = domProps.asDynamic()[name]
operator fun Tag.set(name: String, value: Any) {
domProps.asDynamic()[name] = value
}

// See https://facebook.github.io/react/docs/forms.html
var INPUT.defaultChecked: Boolean
get() = this["defaultChecked"] ?: false
set(value) {
this["defaultChecked"] = value
}

var SELECT.values: Set<String>
get() = (this["value"] ?: arrayOf<String>()).toSet()
set(value) {
this["value"] = value.toTypedArray()
}

var SELECT.value: String
get() = get("value")
set(value) {
set("value", value)
}

val domProps: DOMProps

var key: String
@Deprecated(message = "Write-only property", level = DeprecationLevel.HIDDEN)
get() = error("")
set(value) {
domProps.key = value
}

var ref: RRef
@Deprecated(message = "Write-only property", level = DeprecationLevel.HIDDEN)
get() = error("")
set(value) {
domProps.ref = value
}

fun ref(handler: (dynamic) -> Unit) {
domProps.ref(handler)
}

fun create(): ReactElement = createElement(attrs.tagName, domProps, *childList.toTypedArray())

companion object {
operator fun <T : Tag> invoke(factory: (TagConsumer<Unit>) -> T): RDOMBuilder<T> = RDOMBuilderImpl(factory)
}
}

inline fun <T : Tag> RDOMBuilder<T>.attrs(handler: T.() -> Unit) {
attrs.handler()
}

open class RDOMBuilderImpl<out T : Tag>(factory: (TagConsumer<Unit>) -> T) : RDOMBuilder<T>, RBuilderImpl() {
final override val consumer = object : TagConsumer<Unit> {
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
setProp(attribute, value)
}
Expand All @@ -48,7 +107,7 @@ open class RDOMBuilder<out T : Tag>(factory: (TagConsumer<Unit>) -> T) : RBuilde
sb.append(this)
}
}.block()
props.dangerouslySetInnerHTML = jsObject {
domProps.dangerouslySetInnerHTML = jsObject {
__html = sb.toString()
}
}
Expand All @@ -68,61 +127,10 @@ open class RDOMBuilder<out T : Tag>(factory: (TagConsumer<Unit>) -> T) : RBuilde
override fun finalize() {}
}

val attrs: T = factory(consumer)

operator fun Tag.get(name: String) = props.asDynamic()[name]
operator fun Tag.set(name: String, value: Any) {
props.asDynamic()[name] = value
}

// See https://facebook.github.io/react/docs/forms.html
var INPUT.defaultChecked: Boolean
get() = this["defaultChecked"] ?: false
set(value) {
this["defaultChecked"] = value
}

var SELECT.values: Set<String>
get() = (this["value"] ?: arrayOf<String>()).toSet()
set(value) {
this["value"] = value.toTypedArray()
}

var SELECT.value: String
get() = get("value")
set(value) {
set("value", value)
}

protected val props: DOMProps = jsObject()
final override val attrs: T = factory(consumer)
final override val domProps: DOMProps = jsObject()

init {
attrs.attributesEntries.forEach { setProp(it.key, it.value) }
}

inline fun attrs(handler: T.() -> Unit) {
attrs.handler()
}

var key: String
@Deprecated(message = "Write-only property", level = DeprecationLevel.HIDDEN)
get() = error("")
set(value) {
props.key = value
}

var ref: RRef
@Deprecated(message = "Write-only property", level = DeprecationLevel.HIDDEN)
get() = error("")
set(value) {
props.ref = value
}

fun ref(handler: (dynamic) -> Unit) {
props.ref(handler)
}

open fun create(): ReactElement = createElement(attrs.tagName, props, *childList.toTypedArray())
}


1 change: 1 addition & 0 deletions kotlin-react-redux/src/main/kotlin/react/redux/Dsl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package react.redux

import react.child
import react.RBuilder
import react.RContext
import react.RHandler
Expand Down
77 changes: 49 additions & 28 deletions kotlin-react/src/main/kotlin/react/RBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import kotlin.reflect.*
annotation class ReactDsl

@ReactDsl
open class RBuilder {
val childList = mutableListOf<Any>()
interface RBuilder {
val childList: MutableList<Any>

fun <T : Child> child(element: T): T {
childList.add(element)
Expand Down Expand Up @@ -74,11 +74,6 @@ open class RBuilder {
): ReactElement =
klazz.rClass.invoke(handler)

inline fun <P : RProps, reified C : Component<P, *>> child(
noinline handler: RHandler<P>
): ReactElement =
child(C::class, handler)

fun <T, P : RProps> childFunction(
klazz: KClass<out Component<P, *>>,
handler: RHandler<P>,
Expand All @@ -90,25 +85,13 @@ open class RBuilder {
children = listOf { value: T -> buildElement { children(value) } }
)

inline fun <T, P : RProps, reified C : Component<P, *>> childFunction(
noinline handler: RHandler<P>,
noinline children: RBuilder.(T) -> Unit
): ReactElement =
childFunction(C::class, handler, children)

fun <P : RProps> node(
klazz: KClass<out Component<P, *>>,
props: P,
children: List<Any> = emptyList()
): ReactElement =
klazz.rClass.node(props, children)

inline fun <P : RProps, reified C : Component<P, *>> node(
props: P,
children: List<Any> = emptyList()
): ReactElement =
node(C::class, props, children)

fun RProps.children() {
childList.addAll(Children.toArray(children))
}
Expand Down Expand Up @@ -151,14 +134,14 @@ open class RBuilder {
*
* there will be a proper warning.
* */
fun <T> Collection<T>.renderEach(fn: RBuilder.(T) -> Unit) {
fun <T> Iterable<T>.renderEach(fn: RBuilder.(T) -> Unit) {
childList.add(map {
buildElement { fn(it) }
}.toTypedArray())
}


fun <T> Collection<T>.renderEachIndexed(fn: RBuilder.(Int, T) -> Unit) {
fun <T> Iterable<T>.renderEachIndexed(fn: RBuilder.(Int, T) -> Unit) {
childList.add(mapIndexed { index, it ->
buildElement { fn(index, it) }
}.toTypedArray())
Expand All @@ -174,27 +157,59 @@ open class RBuilder {
}

fun ReactElement.withKey(newKey: Number) = withKey(newKey.toString())

companion object {
operator fun invoke(): RBuilder = RBuilderImpl()
}
}

open class RBuilderMultiple : RBuilder()
inline fun <P : RProps, reified C : Component<P, *>> RBuilder.child(
noinline handler: RHandler<P>
): ReactElement =
child(C::class, handler)

fun buildElements(handler: RBuilder.() -> Unit): dynamic {
val nodes = RBuilder().apply(handler).childList
inline fun <T, P : RProps, reified C : Component<P, *>> RBuilder.childFunction(
noinline handler: RHandler<P>,
noinline children: RBuilder.(T) -> Unit
): ReactElement =
childFunction(C::class, handler, children)

inline fun <P : RProps, reified C : Component<P, *>> RBuilder.node(
props: P,
children: List<Any> = emptyList()
): ReactElement =
node(C::class, props, children)

open class RBuilderImpl: RBuilder {
override val childList = mutableListOf<Any>()
}

open class RBuilderMultiple : RBuilderImpl()

fun <T: RBuilder> buildElements(builder: T, handler: T.() -> Unit): dynamic {
val nodes = builder.apply(handler).childList
return when (nodes.size) {
0 -> null
1 -> nodes.first()
else -> createElement(Fragment, js {}, *nodes.toTypedArray())
}
}

open class RBuilderSingle : RBuilder()
fun buildElements(handler: RBuilder.() -> Unit): dynamic = buildElements(RBuilder(), handler)

inline fun buildElement(handler: RBuilder.() -> Unit): ReactElement =
RBuilder().apply(handler)
open class RBuilderSingle : RBuilderImpl()

inline fun <T: RBuilder> buildElement(rBuilder: T, handler: T.() -> Unit): ReactElement =
rBuilder.apply(handler)
.childList.first()
.unsafeCast<ReactElement>()

open class RElementBuilder<out P : RProps>(open val attrs: P) : RBuilder() {
inline fun buildElement(handler: RBuilder.() -> Unit): ReactElement =
buildElement(RBuilder(), handler)

interface RElementBuilder<out P : RProps> : RBuilder {
val attrs: P

fun attrs(handler: P.() -> Unit) {
attrs.handler()
}
Expand All @@ -216,8 +231,14 @@ open class RElementBuilder<out P : RProps>(open val attrs: P) : RBuilder() {
fun ref(handler: (dynamic) -> Unit) {
attrs.ref(handler)
}

companion object {
operator fun <P : RProps> invoke(attrs: P): RElementBuilder<P> = RElementBuilderImpl(attrs)
}
}

open class RElementBuilderImpl<out P : RProps>(override val attrs: P) : RElementBuilder<P>, RBuilderImpl()

typealias RHandler<P> = RElementBuilder<P>.() -> Unit

fun <P : RProps> forwardRef(handler: RBuilder.(P, RRef) -> Unit): RClass<P> =
Expand Down
32 changes: 25 additions & 7 deletions kotlin-styled/src/main/kotlin/styled/StyledComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,39 @@ interface StyledBuilder<P : WithClassName> {

inline fun StyledBuilder<*>.css(handler: RuleSet) = css.handler()

class StyledElementBuilder<P : WithClassName>(
interface StyledElementBuilder<P : WithClassName>: RElementBuilder<P>, StyledBuilder<P> {
fun create(): ReactElement

companion object {
operator fun <P : WithClassName> invoke(
type: Any,
attrs: P = jsObject()
): StyledElementBuilder<P> = StyledElementBuilderImpl(type, attrs)
}
}

class StyledElementBuilderImpl<P : WithClassName>(
override val type: Any,
attrs: P = jsObject()
) : RElementBuilder<P>(attrs), StyledBuilder<P> {
) : StyledElementBuilder<P>, RElementBuilderImpl<P>(attrs) {
override val css = CSSBuilder()

fun create() = Styled.createElement(type, css, attrs, childList)
override fun create() = Styled.createElement(type, css, attrs, childList)
}

@ReactDsl
class StyledDOMBuilder<out T : Tag>(factory: (TagConsumer<Unit>) -> T) : RDOMBuilder<T>(factory), StyledBuilder<DOMProps> {
override val type: Any = attrs.tagName
override val css = CSSBuilder()
interface StyledDOMBuilder<out T : Tag> : RDOMBuilder<T>, StyledBuilder<DOMProps> {
override val type: Any get() = attrs.tagName

override fun create() = Styled.createElement(type, css, domProps, childList)

override fun create() = Styled.createElement(type, css, props, childList)
companion object {
operator fun <T : Tag> invoke(factory: (TagConsumer<Unit>) -> T): StyledDOMBuilder<T> = StyledDOMBuilderImpl(factory)
}
}

class StyledDOMBuilderImpl<out T : Tag>(factory: (TagConsumer<Unit>) -> T) : StyledDOMBuilder<T>, RDOMBuilderImpl<T>(factory) {
override val css = CSSBuilder()
}

typealias StyledHandler<P> = StyledElementBuilder<P>.() -> Unit
Expand Down

0 comments on commit 4115486

Please sign in to comment.