|
|
@ -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) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|