From 6b209ebd87d92536d96935ed32c49be6594c7a4f Mon Sep 17 00:00:00 2001 From: Shagen Ogandzhanian Date: Wed, 25 Aug 2021 22:35:24 +0200 Subject: [PATCH] Prepare DomApplier and related code for working with SVG entities --- .../web/attributes/SyntheticEventListener.kt | 4 +- .../jetbrains/compose/web/css/StyleBuilder.kt | 12 +- .../jetbrains/compose/web/elements/Base.kt | 104 +++++++++++------- .../jsTest/kotlin/elements/AttributesTests.kt | 2 +- .../web/internal/runtime/DomApplier.kt | 60 +++------- 5 files changed, 82 insertions(+), 100 deletions(-) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/SyntheticEventListener.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/SyntheticEventListener.kt index 5a946fd291..66e0f4a482 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/SyntheticEventListener.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/SyntheticEventListener.kt @@ -9,8 +9,8 @@ import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.CHAN import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.INPUT import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.SELECT import org.jetbrains.compose.web.events.* -import org.jetbrains.compose.web.internal.runtime.DomNodeWrapper import org.jetbrains.compose.web.internal.runtime.ComposeWebInternalApi +import org.jetbrains.compose.web.internal.runtime.NamedEventListener import org.w3c.dom.DragEvent import org.w3c.dom.TouchEvent import org.w3c.dom.clipboard.ClipboardEvent @@ -21,7 +21,7 @@ open class SyntheticEventListener> internal constructor( val event: String, val options: Options, val listener: (T) -> Unit -) : EventListener, DomNodeWrapper.NamedEventListener { +) : EventListener, NamedEventListener { override val name: String = event diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt index 3855ffee3e..595b078474 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt @@ -133,7 +133,7 @@ interface StyleHolder { @OptIn(ComposeWebInternalApi::class) @Suppress("EqualsOrHashCode") -open class StyleBuilderImpl : StyleBuilder, StyleHolder, DomElementWrapper.StyleDeclarationsApplier { +open class StyleBuilderImpl : StyleBuilder, StyleHolder { override val properties: MutableStylePropertyList = mutableListOf() override val variables: MutableStylePropertyList = mutableListOf() @@ -157,16 +157,6 @@ open class StyleBuilderImpl : StyleBuilder, StyleHolder, DomElementWrapper.Style properties.addAll(sb.properties) variables.addAll(sb.variables) } - - override fun applyToNodeStyle(nodeStyle: CSSStyleDeclaration) { - properties.forEach { (name, value) -> - nodeStyle.setProperty(name, value.toString()) - } - - variables.forEach { (name, value) -> - nodeStyle.setProperty(name, value.toString()) - } - } } data class StylePropertyDeclaration( 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 16c34edb0e..b00eef957e 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 @@ -1,33 +1,25 @@ package org.jetbrains.compose.web.dom -import androidx.compose.runtime.Applier -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ComposeCompilerApi -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.DisposableEffectResult -import androidx.compose.runtime.DisposableEffectScope -import androidx.compose.runtime.ExplicitGroupsComposable -import androidx.compose.runtime.SkippableUpdater -import androidx.compose.runtime.currentComposer -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import org.jetbrains.compose.web.attributes.AttrsBuilder import org.jetbrains.compose.web.ExperimentalComposeWebApi -import org.jetbrains.compose.web.internal.runtime.DomApplier +import org.jetbrains.compose.web.css.StyleHolder import org.jetbrains.compose.web.internal.runtime.DomElementWrapper import org.jetbrains.compose.web.internal.runtime.ComposeWebInternalApi import org.w3c.dom.Element import org.w3c.dom.HTMLElement +import org.w3c.dom.css.ElementCSSInlineStyle +import org.w3c.dom.svg.SVGElement @OptIn(ComposeCompilerApi::class) @Composable @ExplicitGroupsComposable -inline fun > ComposeDomNode( +private inline fun ComposeDomNode( noinline factory: () -> T, elementScope: TScope, noinline attrsSkippableUpdate: @Composable SkippableUpdater.() -> Unit, noinline content: (@Composable TScope.() -> Unit)? ) { - if (currentComposer.applier !is E) error("Invalid applier") currentComposer.startNode() if (currentComposer.inserting) { currentComposer.createNode(factory) @@ -35,9 +27,7 @@ inline fun > ComposeDomNode( currentComposer.useNode() } - SkippableUpdater(currentComposer).apply { - attrsSkippableUpdate() - } + attrsSkippableUpdate.invoke(SkippableUpdater(currentComposer)) currentComposer.startReplaceableGroup(0x7ab4aae9) content?.invoke(elementScope) @@ -45,9 +35,47 @@ inline fun > ComposeDomNode( currentComposer.endNode() } -class DisposableEffectHolder( - var effect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null -) + +@OptIn(ComposeWebInternalApi::class) +private fun DomElementWrapper.updateProperties(applicators: List Unit, Any>>) { + node.removeAttribute("class") + + applicators.forEach { (applicator, item) -> + applicator(node, item) + } +} + +@OptIn(ComposeWebInternalApi::class) +private fun DomElementWrapper.updateStyleDeclarations(styleApplier: StyleHolder) { + when (node) { + is HTMLElement, is SVGElement -> { + node.removeAttribute("style") + + val style = node.unsafeCast().style + + styleApplier.properties.forEach { (name, value) -> + style.setProperty(name, value.toString()) + } + + styleApplier.variables.forEach { (name, value) -> + style.setProperty(name, value.toString()) + } + } + } +} + +@OptIn(ComposeWebInternalApi::class) +fun DomElementWrapper.updateAttrs(attrs: Map) { + node.getAttributeNames().forEach { name -> + if (name == "style") return@forEach + node.removeAttribute(name) + } + + attrs.forEach { + node.setAttribute(it.key, it.value) + } +} + @OptIn(ComposeWebInternalApi::class) @Composable @@ -56,38 +84,36 @@ fun TagElement( applyAttrs: (AttrsBuilder.() -> Unit)?, content: (@Composable ElementScope.() -> Unit)? ) { - val scope = remember { ElementScopeImpl() } - val refEffect = remember { DisposableEffectHolder() } + val scope = remember { ElementScopeImpl() } + var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null - ComposeDomNode, DomElementWrapper, DomApplier>( + ComposeDomNode, DomElementWrapper>( factory = { - DomElementWrapper(elementBuilder.create() as HTMLElement).also { - scope.element = it.node.unsafeCast() - } + val node = elementBuilder.create() + scope.element = node + DomElementWrapper(node) }, attrsSkippableUpdate = { - val attrsApplied = AttrsBuilder().also { - if (applyAttrs != null) { - it.applyAttrs() - } - } - refEffect.effect = attrsApplied.refEffect - val attrsCollected = attrsApplied.collect() - val events = attrsApplied.collectListeners() + val attrsBuilder = AttrsBuilder() + applyAttrs?.invoke(attrsBuilder) + + refEffect = attrsBuilder.refEffect update { - set(attrsCollected, DomElementWrapper::updateAttrs) - set(events, DomElementWrapper::updateEventListeners) - set(attrsApplied.propertyUpdates, DomElementWrapper::updateProperties) - set(attrsApplied.styleBuilder, DomElementWrapper::updateStyleDeclarations) + set(attrsBuilder.collect(), DomElementWrapper::updateAttrs) + set(attrsBuilder.collectListeners(), DomElementWrapper::updateEventListeners) + set(attrsBuilder.propertyUpdates, DomElementWrapper::updateProperties) + set(attrsBuilder.styleBuilder, DomElementWrapper::updateStyleDeclarations) } }, elementScope = scope, content = content ) - DisposableEffect(null) { - refEffect.effect?.invoke(this, scope.element) ?: onDispose {} + refEffect?.let { effect -> + DisposableEffect(null) { + effect.invoke(this, scope.element) + } } } diff --git a/web/core/src/jsTest/kotlin/elements/AttributesTests.kt b/web/core/src/jsTest/kotlin/elements/AttributesTests.kt index dfc7fc0078..ce55090ac2 100644 --- a/web/core/src/jsTest/kotlin/elements/AttributesTests.kt +++ b/web/core/src/jsTest/kotlin/elements/AttributesTests.kt @@ -400,7 +400,7 @@ class AttributesTests { waitForRecompositionComplete() assertEquals( - expected = "", + expected = "", actual = currentChild().outerHTML ) } diff --git a/web/internal-web-core-runtime/src/jsMain/kotlin/org/jetbrains/compose/web/internal/runtime/DomApplier.kt b/web/internal-web-core-runtime/src/jsMain/kotlin/org/jetbrains/compose/web/internal/runtime/DomApplier.kt index 6906a0c986..bef7baf0cb 100644 --- a/web/internal-web-core-runtime/src/jsMain/kotlin/org/jetbrains/compose/web/internal/runtime/DomApplier.kt +++ b/web/internal-web-core-runtime/src/jsMain/kotlin/org/jetbrains/compose/web/internal/runtime/DomApplier.kt @@ -35,28 +35,12 @@ class DomApplier( @ComposeWebInternalApi -open class DomNodeWrapper(open val node: Node) { - - @ComposeWebInternalApi - interface NamedEventListener : EventListener { - val name: String - } - - private var currentListeners = emptyList() - - fun updateEventListeners(list: List) { - val htmlElement = node as? HTMLElement ?: return - - currentListeners.forEach { - htmlElement.removeEventListener(it.name, it) - } - - currentListeners = list +interface NamedEventListener : EventListener { + val name: String +} - currentListeners.forEach { - htmlElement.addEventListener(it.name, it) - } - } +@ComposeWebInternalApi +open class DomNodeWrapper(open val node: Node) { fun insert(index: Int, nodeWrapper: DomNodeWrapper) { val length = node.childNodes.length @@ -90,36 +74,18 @@ open class DomNodeWrapper(open val node: Node) { } @ComposeWebInternalApi -class DomElementWrapper(override val node: HTMLElement): DomNodeWrapper(node) { - private var currentAttrs: Map? = null +class DomElementWrapper(override val node: Element): DomNodeWrapper(node) { + private var currentListeners = emptyList() - fun updateAttrs(attrs: Map) { - currentAttrs?.forEach { - node.removeAttribute(it.key) + fun updateEventListeners(list: List) { + currentListeners.forEach { + node.removeEventListener(it.name, it) } - attrs.forEach { - node.setAttribute(it.key, it.value) - } - currentAttrs = attrs - } - - fun updateProperties(list: List Unit, Any>>) { - if (node.className.isNotEmpty()) node.className = "" + currentListeners = list - list.forEach { - it.first(node, it.second) + currentListeners.forEach { + node.addEventListener(it.name, it) } } - - @ComposeWebInternalApi - fun interface StyleDeclarationsApplier { - @ComposeWebInternalApi - fun applyToNodeStyle(nodeStyle: CSSStyleDeclaration) - } - - fun updateStyleDeclarations(styleApplier: StyleDeclarationsApplier) { - node.removeAttribute("style") - styleApplier.applyToNodeStyle(node.style) - } }