From 4b21c3b5a913afbb530a8ffb51542b1c3fe1f363 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Fri, 11 Feb 2022 10:34:44 +0100 Subject: [PATCH 1/3] 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. --- .../jetbrains/compose/web/elements/Base.kt | 14 ++++-- .../compose/web/elements/Elements.kt | 8 ++- .../jsTest/kotlin/elements/ElementsTests.kt | 50 +++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt index 0cebc82819b..b1866c1ca36 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt @@ -154,8 +154,12 @@ fun TagElement( tagName: String, applyAttrs: AttrsScope.() -> Unit, content: (@Composable ElementScope.() -> Unit)? -) = TagElement( - elementBuilder = ElementBuilder.createBuilder(tagName), - applyAttrs = applyAttrs, - content = content -) +) { + key(tagName) { + TagElement( + elementBuilder = ElementBuilder.createBuilder(tagName), + applyAttrs = applyAttrs, + content = content + ) + } +} diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt index 3cede7cf448..5ee11e84954 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt @@ -150,14 +150,18 @@ private val Td: ElementBuilder = ElementBuilderImplementat private val Tbody: ElementBuilder = ElementBuilderImplementation("tbody") private val Tfoot: ElementBuilder = ElementBuilderImplementation("tfoot") -val Style: ElementBuilder = ElementBuilderImplementation("style") +internal val Style: ElementBuilder = ElementBuilderImplementation("style") fun interface ElementBuilder { fun create(): TElement companion object { + internal val buildersCache = mutableMapOf>() + fun createBuilder(tagName: String): ElementBuilder { - return object : ElementBuilderImplementation(tagName) {} + return buildersCache.getOrPut(tagName) { + ElementBuilderImplementation(tagName) + }.unsafeCast>() } } } diff --git a/web/core/src/jsTest/kotlin/elements/ElementsTests.kt b/web/core/src/jsTest/kotlin/elements/ElementsTests.kt index d2b006d2cd1..090f88c375c 100644 --- a/web/core/src/jsTest/kotlin/elements/ElementsTests.kt +++ b/web/core/src/jsTest/kotlin/elements/ElementsTests.kt @@ -136,6 +136,56 @@ class ElementsTests { assertEquals("
CUSTOM
", root.outerHTML) } + @Test + fun testElementBuilderCreate() { + val custom = ElementBuilder.createBuilder("custom") + val div = ElementBuilder.createBuilder("div") + val b = ElementBuilder.createBuilder("b") + val abc = ElementBuilder.createBuilder("abc") + + val expectedKeys = setOf("custom", "div", "b", "abc") + assertEquals(expectedKeys, ElementBuilder.buildersCache.keys.intersect(expectedKeys)) + + assertEquals("custom", custom.create().nodeName.lowercase()) + assertEquals("div", div.create().nodeName.lowercase()) + assertEquals("b", b.create().nodeName.lowercase()) + assertEquals("abc", abc.create().nodeName.lowercase()) + } + + @Test + @OptIn(ExperimentalComposeWebApi::class) + fun rawCreationAndTagChanges() = runTest { + @Composable + fun CustomElement( + tagName: String, + attrs: AttrsScope.() -> Unit, + content: ContentBuilder? = null + ) { + TagElement( + tagName = tagName, + applyAttrs = attrs, + content + ) + } + + var tagName by mutableStateOf("custom") + + composition { + CustomElement(tagName, { + id("container") + }) { + Text("CUSTOM") + } + } + + assertEquals("
CUSTOM
", root.outerHTML) + + tagName = "anothercustom" + waitForRecompositionComplete() + + assertEquals("
CUSTOM
", root.outerHTML) + } + @Test fun elementBuilderShouldBeCalledOnce() = runTest { var counter = 0 From 538fa805d4d12c65628ee0c7e05844225e7ca151 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Fri, 11 Feb 2022 10:39:27 +0100 Subject: [PATCH 2/3] add comments --- .../jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt index 5ee11e84954..4f297473184 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt @@ -156,6 +156,7 @@ fun interface ElementBuilder { fun create(): TElement companion object { + // it's internal only for testing purposes internal val buildersCache = mutableMapOf>() fun createBuilder(tagName: String): ElementBuilder { From b64cbff4978722aaeb453dc4243cc864b875cab1 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Wed, 16 Feb 2022 11:31:27 +0100 Subject: [PATCH 3/3] update PR according to discussion --- .../kotlin/org/jetbrains/compose/web/elements/Base.kt | 10 +++++++++- .../org/jetbrains/compose/web/elements/Elements.kt | 5 +++-- web/core/src/jsTest/kotlin/elements/ElementsTests.kt | 8 ++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt index b1866c1ca36..f566bc69975 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt @@ -148,8 +148,16 @@ fun 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 -@ExperimentalComposeWebApi fun TagElement( tagName: String, applyAttrs: AttrsScope.() -> Unit, diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt index 4f297473184..896dc730ddc 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt @@ -160,8 +160,9 @@ fun interface ElementBuilder { internal val buildersCache = mutableMapOf>() fun createBuilder(tagName: String): ElementBuilder { - return buildersCache.getOrPut(tagName) { - ElementBuilderImplementation(tagName) + val tagLowercase = tagName.lowercase() + return buildersCache.getOrPut(tagLowercase) { + ElementBuilderImplementation(tagLowercase) }.unsafeCast>() } } diff --git a/web/core/src/jsTest/kotlin/elements/ElementsTests.kt b/web/core/src/jsTest/kotlin/elements/ElementsTests.kt index 090f88c375c..911d0723a85 100644 --- a/web/core/src/jsTest/kotlin/elements/ElementsTests.kt +++ b/web/core/src/jsTest/kotlin/elements/ElementsTests.kt @@ -146,10 +146,10 @@ class ElementsTests { val expectedKeys = setOf("custom", "div", "b", "abc") assertEquals(expectedKeys, ElementBuilder.buildersCache.keys.intersect(expectedKeys)) - assertEquals("custom", custom.create().nodeName.lowercase()) - assertEquals("div", div.create().nodeName.lowercase()) - assertEquals("b", b.create().nodeName.lowercase()) - assertEquals("abc", abc.create().nodeName.lowercase()) + assertEquals("CUSTOM", custom.create().nodeName) + assertEquals("DIV", div.create().nodeName) + assertEquals("B", b.create().nodeName) + assertEquals("ABC", abc.create().nodeName) } @Test