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. 104
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt
  4. 2
      web/core/src/jsTest/kotlin/elements/AttributesTests.kt
  5. 60
      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.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<T : SyntheticEvent<*>> internal constructor(
val event: String,
val options: Options,
val listener: (T) -> Unit
) : EventListener, DomNodeWrapper.NamedEventListener {
) : EventListener, NamedEventListener {
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)
@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(

104
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 <TScope, T, reified E : Applier<*>> ComposeDomNode(
private inline fun <TScope, T> ComposeDomNode(
noinline factory: () -> T,
elementScope: TScope,
noinline attrsSkippableUpdate: @Composable SkippableUpdater<T>.() -> 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 <TScope, T, reified E : Applier<*>> ComposeDomNode(
currentComposer.useNode()
}
SkippableUpdater<T>(currentComposer).apply {
attrsSkippableUpdate()
}
attrsSkippableUpdate.invoke(SkippableUpdater(currentComposer))
currentComposer.startReplaceableGroup(0x7ab4aae9)
content?.invoke(elementScope)
@ -45,9 +35,47 @@ inline fun <TScope, T, reified E : Applier<*>> ComposeDomNode(
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)
@Composable
@ -56,38 +84,36 @@ fun <TElement : Element> TagElement(
applyAttrs: (AttrsBuilder<TElement>.() -> Unit)?,
content: (@Composable ElementScope<TElement>.() -> Unit)?
) {
val scope = remember { ElementScopeImpl<TElement>() }
val refEffect = remember { DisposableEffectHolder<TElement>() }
val scope = remember { ElementScopeImpl<TElement>() }
var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null
ComposeDomNode<ElementScope<TElement>, DomElementWrapper, DomApplier>(
ComposeDomNode<ElementScope<TElement>, DomElementWrapper>(
factory = {
DomElementWrapper(elementBuilder.create() as HTMLElement).also {
scope.element = it.node.unsafeCast<TElement>()
}
val node = elementBuilder.create()
scope.element = node
DomElementWrapper(node)
},
attrsSkippableUpdate = {
val attrsApplied = AttrsBuilder<TElement>().also {
if (applyAttrs != null) {
it.applyAttrs()
}
}
refEffect.effect = attrsApplied.refEffect
val attrsCollected = attrsApplied.collect()
val events = attrsApplied.collectListeners()
val attrsBuilder = AttrsBuilder<TElement>()
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)
}
}
}

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

@ -400,7 +400,7 @@ class AttributesTests {
waitForRecompositionComplete()
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
)
}

60
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<NamedEventListener>()
fun updateEventListeners(list: List<NamedEventListener>) {
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<String, String>? = null
class DomElementWrapper(override val node: Element): DomNodeWrapper(node) {
private var currentListeners = emptyList<NamedEventListener>()
fun updateAttrs(attrs: Map<String, String>) {
currentAttrs?.forEach {
node.removeAttribute(it.key)
fun updateEventListeners(list: List<NamedEventListener>) {
currentListeners.forEach {
node.removeEventListener(it.name, it)
}
attrs.forEach {
node.setAttribute(it.key, it.value)
}
currentAttrs = attrs
}
fun updateProperties(list: List<Pair<(Element, Any) -> 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)
}
}

Loading…
Cancel
Save