Browse Source

Prepare DomApplier and related code for working with SVG entities

pull/1118/head
Shagen Ogandzhanian 3 years ago
parent
commit
6b209ebd87
  1. 4
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/SyntheticEventListener.kt
  2. 12
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt
  3. 100
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt
  4. 2
      web/core/src/jsTest/kotlin/elements/AttributesTests.kt
  5. 54
      web/internal-web-core-runtime/src/jsMain/kotlin/org/jetbrains/compose/web/internal/runtime/DomApplier.kt

4
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.INPUT
import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.SELECT import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.SELECT
import org.jetbrains.compose.web.events.* 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.ComposeWebInternalApi
import org.jetbrains.compose.web.internal.runtime.NamedEventListener
import org.w3c.dom.DragEvent import org.w3c.dom.DragEvent
import org.w3c.dom.TouchEvent import org.w3c.dom.TouchEvent
import org.w3c.dom.clipboard.ClipboardEvent import org.w3c.dom.clipboard.ClipboardEvent
@ -21,7 +21,7 @@ open class SyntheticEventListener<T : SyntheticEvent<*>> internal constructor(
val event: String, val event: String,
val options: Options, val options: Options,
val listener: (T) -> Unit val listener: (T) -> Unit
) : EventListener, DomNodeWrapper.NamedEventListener { ) : EventListener, NamedEventListener {
override val name: String = event override val name: String = event

12
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt

@ -133,7 +133,7 @@ interface StyleHolder {
@OptIn(ComposeWebInternalApi::class) @OptIn(ComposeWebInternalApi::class)
@Suppress("EqualsOrHashCode") @Suppress("EqualsOrHashCode")
open class StyleBuilderImpl : StyleBuilder, StyleHolder, DomElementWrapper.StyleDeclarationsApplier { open class StyleBuilderImpl : StyleBuilder, StyleHolder {
override val properties: MutableStylePropertyList = mutableListOf() override val properties: MutableStylePropertyList = mutableListOf()
override val variables: MutableStylePropertyList = mutableListOf() override val variables: MutableStylePropertyList = mutableListOf()
@ -157,16 +157,6 @@ open class StyleBuilderImpl : StyleBuilder, StyleHolder, DomElementWrapper.Style
properties.addAll(sb.properties) properties.addAll(sb.properties)
variables.addAll(sb.variables) 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( data class StylePropertyDeclaration(

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

@ -1,33 +1,25 @@
package org.jetbrains.compose.web.dom package org.jetbrains.compose.web.dom
import androidx.compose.runtime.Applier import androidx.compose.runtime.*
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 org.jetbrains.compose.web.attributes.AttrsBuilder import org.jetbrains.compose.web.attributes.AttrsBuilder
import org.jetbrains.compose.web.ExperimentalComposeWebApi 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.DomElementWrapper
import org.jetbrains.compose.web.internal.runtime.ComposeWebInternalApi import org.jetbrains.compose.web.internal.runtime.ComposeWebInternalApi
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.css.ElementCSSInlineStyle
import org.w3c.dom.svg.SVGElement
@OptIn(ComposeCompilerApi::class) @OptIn(ComposeCompilerApi::class)
@Composable @Composable
@ExplicitGroupsComposable @ExplicitGroupsComposable
inline fun <TScope, T, reified E : Applier<*>> ComposeDomNode( private inline fun <TScope, T> ComposeDomNode(
noinline factory: () -> T, noinline factory: () -> T,
elementScope: TScope, elementScope: TScope,
noinline attrsSkippableUpdate: @Composable SkippableUpdater<T>.() -> Unit, noinline attrsSkippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
noinline content: (@Composable TScope.() -> Unit)? noinline content: (@Composable TScope.() -> Unit)?
) { ) {
if (currentComposer.applier !is E) error("Invalid applier")
currentComposer.startNode() currentComposer.startNode()
if (currentComposer.inserting) { if (currentComposer.inserting) {
currentComposer.createNode(factory) currentComposer.createNode(factory)
@ -35,9 +27,7 @@ inline fun <TScope, T, reified E : Applier<*>> ComposeDomNode(
currentComposer.useNode() currentComposer.useNode()
} }
SkippableUpdater<T>(currentComposer).apply { attrsSkippableUpdate.invoke(SkippableUpdater(currentComposer))
attrsSkippableUpdate()
}
currentComposer.startReplaceableGroup(0x7ab4aae9) currentComposer.startReplaceableGroup(0x7ab4aae9)
content?.invoke(elementScope) content?.invoke(elementScope)
@ -45,9 +35,47 @@ inline fun <TScope, T, reified E : Applier<*>> ComposeDomNode(
currentComposer.endNode() currentComposer.endNode()
} }
class DisposableEffectHolder<TElement : Element>(
var effect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null @OptIn(ComposeWebInternalApi::class)
) private fun DomElementWrapper.updateProperties(applicators: List<Pair<(Element, Any) -> 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<ElementCSSInlineStyle>().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<String, String>) {
node.getAttributeNames().forEach { name ->
if (name == "style") return@forEach
node.removeAttribute(name)
}
attrs.forEach {
node.setAttribute(it.key, it.value)
}
}
@OptIn(ComposeWebInternalApi::class) @OptIn(ComposeWebInternalApi::class)
@Composable @Composable
@ -57,37 +85,35 @@ fun <TElement : Element> TagElement(
content: (@Composable ElementScope<TElement>.() -> Unit)? content: (@Composable ElementScope<TElement>.() -> Unit)?
) { ) {
val scope = remember { ElementScopeImpl<TElement>() } val scope = remember { ElementScopeImpl<TElement>() }
val refEffect = remember { DisposableEffectHolder<TElement>() } var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null
ComposeDomNode<ElementScope<TElement>, DomElementWrapper, DomApplier>( ComposeDomNode<ElementScope<TElement>, DomElementWrapper>(
factory = { factory = {
DomElementWrapper(elementBuilder.create() as HTMLElement).also { val node = elementBuilder.create()
scope.element = it.node.unsafeCast<TElement>() scope.element = node
} DomElementWrapper(node)
}, },
attrsSkippableUpdate = { attrsSkippableUpdate = {
val attrsApplied = AttrsBuilder<TElement>().also { val attrsBuilder = AttrsBuilder<TElement>()
if (applyAttrs != null) { applyAttrs?.invoke(attrsBuilder)
it.applyAttrs()
} refEffect = attrsBuilder.refEffect
}
refEffect.effect = attrsApplied.refEffect
val attrsCollected = attrsApplied.collect()
val events = attrsApplied.collectListeners()
update { update {
set(attrsCollected, DomElementWrapper::updateAttrs) set(attrsBuilder.collect(), DomElementWrapper::updateAttrs)
set(events, DomElementWrapper::updateEventListeners) set(attrsBuilder.collectListeners(), DomElementWrapper::updateEventListeners)
set(attrsApplied.propertyUpdates, DomElementWrapper::updateProperties) set(attrsBuilder.propertyUpdates, DomElementWrapper::updateProperties)
set(attrsApplied.styleBuilder, DomElementWrapper::updateStyleDeclarations) set(attrsBuilder.styleBuilder, DomElementWrapper::updateStyleDeclarations)
} }
}, },
elementScope = scope, elementScope = scope,
content = content content = content
) )
refEffect?.let { effect ->
DisposableEffect(null) { DisposableEffect(null) {
refEffect.effect?.invoke(this, scope.element) ?: onDispose {} effect.invoke(this, scope.element)
}
} }
} }

2
web/core/src/jsTest/kotlin/elements/AttributesTests.kt

@ -400,7 +400,7 @@ class AttributesTests {
waitForRecompositionComplete() waitForRecompositionComplete()
assertEquals( assertEquals(
expected = "<button class=\"a b\" style=\"color: red;\" value=\"buttonValue\">Button</button>", expected = "<button style=\"color: red;\" value=\"buttonValue\" class=\"a b\">Button</button>",
actual = currentChild().outerHTML actual = currentChild().outerHTML
) )
} }

54
web/internal-web-core-runtime/src/jsMain/kotlin/org/jetbrains/compose/web/internal/runtime/DomApplier.kt

@ -34,29 +34,13 @@ class DomApplier(
} }
@ComposeWebInternalApi
open class DomNodeWrapper(open val node: Node) {
@ComposeWebInternalApi @ComposeWebInternalApi
interface NamedEventListener : EventListener { interface NamedEventListener : EventListener {
val name: String val name: String
} }
private var currentListeners = emptyList<NamedEventListener>() @ComposeWebInternalApi
open class DomNodeWrapper(open val node: Node) {
fun updateEventListeners(list: List<NamedEventListener>) {
val htmlElement = node as? HTMLElement ?: return
currentListeners.forEach {
htmlElement.removeEventListener(it.name, it)
}
currentListeners = list
currentListeners.forEach {
htmlElement.addEventListener(it.name, it)
}
}
fun insert(index: Int, nodeWrapper: DomNodeWrapper) { fun insert(index: Int, nodeWrapper: DomNodeWrapper) {
val length = node.childNodes.length val length = node.childNodes.length
@ -90,36 +74,18 @@ open class DomNodeWrapper(open val node: Node) {
} }
@ComposeWebInternalApi @ComposeWebInternalApi
class DomElementWrapper(override val node: HTMLElement): DomNodeWrapper(node) { class DomElementWrapper(override val node: Element): DomNodeWrapper(node) {
private var currentAttrs: Map<String, String>? = null private var currentListeners = emptyList<NamedEventListener>()
fun updateAttrs(attrs: Map<String, String>) {
currentAttrs?.forEach {
node.removeAttribute(it.key)
}
attrs.forEach { fun updateEventListeners(list: List<NamedEventListener>) {
node.setAttribute(it.key, it.value) currentListeners.forEach {
} node.removeEventListener(it.name, it)
currentAttrs = attrs
} }
fun updateProperties(list: List<Pair<(Element, Any) -> Unit, Any>>) { currentListeners = list
if (node.className.isNotEmpty()) node.className = ""
list.forEach {
it.first(node, it.second)
}
}
@ComposeWebInternalApi currentListeners.forEach {
fun interface StyleDeclarationsApplier { node.addEventListener(it.name, it)
@ComposeWebInternalApi
fun applyToNodeStyle(nodeStyle: CSSStyleDeclaration)
} }
fun updateStyleDeclarations(styleApplier: StyleDeclarationsApplier) {
node.removeAttribute("style")
styleApplier.applyToNodeStyle(node.style)
} }
} }

Loading…
Cancel
Save