Browse Source

web: update TagElement with tagName: String (#1827)

* web: update TagElement with tagName: String

Enable changing the tagName value. This will delete the initial html element and create a new one with a new tagName.

Cache ElementBuilder instances.

* add comments

* update PR according to discussion

Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
pull/1868/head
Oleksandr Karpovich 3 years ago committed by GitHub
parent
commit
5b496eb191
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt
  2. 10
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt
  3. 50
      web/core/src/jsTest/kotlin/elements/ElementsTests.kt

24
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt

@ -148,14 +148,26 @@ fun <TElement : Element> TagElement(
} }
} }
/**
* @param tagName - the name of the tag that needs to be created.
* It's best to use constant values for [tagName].
* If variable [tagName] needed, consider wrapping TagElement calls into an if...else:
*
* ```
* if (useDiv) TagElement("div",...) else TagElement("span", ...)
* ```
*/
@Composable @Composable
@ExperimentalComposeWebApi
fun <TElement : Element> TagElement( fun <TElement : Element> TagElement(
tagName: String, tagName: String,
applyAttrs: (AttrsScope<TElement>.() -> Unit)?, applyAttrs: (AttrsScope<TElement>.() -> Unit)?,
content: (@Composable ElementScope<TElement>.() -> Unit)? content: (@Composable ElementScope<TElement>.() -> Unit)?
) = TagElement( ) {
elementBuilder = ElementBuilder.createBuilder(tagName), key(tagName) {
applyAttrs = applyAttrs, TagElement(
content = content elementBuilder = ElementBuilder.createBuilder(tagName),
) applyAttrs = applyAttrs,
content = content
)
}
}

10
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt

@ -152,14 +152,20 @@ private val Td: ElementBuilder<HTMLTableCellElement> = ElementBuilderImplementat
private val Tbody: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("tbody") private val Tbody: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("tbody")
private val Tfoot: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("tfoot") private val Tfoot: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("tfoot")
val Style: ElementBuilder<HTMLStyleElement> = ElementBuilderImplementation("style") internal val Style: ElementBuilder<HTMLStyleElement> = ElementBuilderImplementation("style")
fun interface ElementBuilder<TElement : Element> { fun interface ElementBuilder<TElement : Element> {
fun create(): TElement fun create(): TElement
companion object { companion object {
// it's internal only for testing purposes
internal val buildersCache = mutableMapOf<String, ElementBuilder<*>>()
fun <TElement : Element> createBuilder(tagName: String): ElementBuilder<TElement> { fun <TElement : Element> createBuilder(tagName: String): ElementBuilder<TElement> {
return object : ElementBuilderImplementation<TElement>(tagName) {} val tagLowercase = tagName.lowercase()
return buildersCache.getOrPut(tagLowercase) {
ElementBuilderImplementation<TElement>(tagLowercase)
}.unsafeCast<ElementBuilder<TElement>>()
} }
} }
} }

50
web/core/src/jsTest/kotlin/elements/ElementsTests.kt

@ -135,6 +135,56 @@ class ElementsTests {
assertEquals("<div><custom id=\"container\">CUSTOM</custom></div>", root.outerHTML) assertEquals("<div><custom id=\"container\">CUSTOM</custom></div>", root.outerHTML)
} }
@Test
fun testElementBuilderCreate() {
val custom = ElementBuilder.createBuilder<HTMLElement>("custom")
val div = ElementBuilder.createBuilder<HTMLElement>("div")
val b = ElementBuilder.createBuilder<HTMLElement>("b")
val abc = ElementBuilder.createBuilder<HTMLElement>("abc")
val expectedKeys = setOf("custom", "div", "b", "abc")
assertEquals(expectedKeys, ElementBuilder.buildersCache.keys.intersect(expectedKeys))
assertEquals("CUSTOM", custom.create().nodeName)
assertEquals("DIV", div.create().nodeName)
assertEquals("B", b.create().nodeName)
assertEquals("ABC", abc.create().nodeName)
}
@Test
@OptIn(ExperimentalComposeWebApi::class)
fun rawCreationAndTagChanges() = runTest {
@Composable
fun CustomElement(
tagName: String,
attrs: AttrsScope<HTMLElement>.() -> Unit,
content: ContentBuilder<HTMLElement>? = null
) {
TagElement(
tagName = tagName,
applyAttrs = attrs,
content
)
}
var tagName by mutableStateOf("custom")
composition {
CustomElement(tagName, {
id("container")
}) {
Text("CUSTOM")
}
}
assertEquals("<div><custom id=\"container\">CUSTOM</custom></div>", root.outerHTML)
tagName = "anothercustom"
waitForRecompositionComplete()
assertEquals("<div><anothercustom id=\"container\">CUSTOM</anothercustom></div>", root.outerHTML)
}
@Test @Test
fun elementBuilderShouldBeCalledOnce() = runTest { fun elementBuilderShouldBeCalledOnce() = runTest {
var counter = 0 var counter = 0

Loading…
Cancel
Save