diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt index e14a1ccf20..c9cabdbf52 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt @@ -8,6 +8,7 @@ import androidx.compose.web.elements.setProperty import androidx.compose.web.elements.setVariable import kotlinx.browser.document import kotlinx.dom.clear +import org.w3c.dom.Element import org.w3c.dom.HTMLElement import org.w3c.dom.Node import org.w3c.dom.get @@ -38,20 +39,10 @@ class DomApplier( } } -class DomNodeWrapper(val node: Node) { - +open class DomNodeWrapper(open val node: Node) { constructor(tag: String) : this(document.createElement(tag)) - private var currentListeners: List> = emptyList() - private var currentAttrs: Map = emptyMap() - - fun updateProperties(list: List Unit, Any>>) { - val htmlElement = node as? HTMLElement ?: return - - if (node.className.isNotEmpty()) node.className = "" - - list.forEach { it.first(htmlElement, it.second) } - } + private var currentListeners = emptyList>() fun updateEventListeners(list: List>) { val htmlElement = node as? HTMLElement ?: return @@ -67,29 +58,6 @@ class DomNodeWrapper(val node: Node) { } } - fun updateAttrs(attrs: Map) { - val htmlElement = node as? HTMLElement ?: return - currentAttrs.forEach { - htmlElement.removeAttribute(it.key) - } - currentAttrs = attrs - currentAttrs.forEach { - if (it.value != null) htmlElement.setAttribute(it.key, it.value ?: "") - } - } - - fun updateStyleDeclarations(style: StyleHolder?) { - val htmlElement = node as? HTMLElement ?: return - htmlElement.removeAttribute("style") - - style?.properties?.forEach { (name, value) -> - setProperty(htmlElement.attributeStyleMap, name, value) - } - style?.variables?.forEach { (name, value) -> - setVariable(htmlElement.style, name, value) - } - } - fun insert(index: Int, nodeWrapper: DomNodeWrapper) { val length = node.childNodes.length if (index < length) { @@ -119,23 +87,36 @@ class DomNodeWrapper(val node: Node) { node.insertBefore(child, node.childNodes[toIndex]!!) } } +} + - companion object { +class DomElementWrapper(override val node: HTMLElement): DomNodeWrapper(node) { + private var currentAttrs = emptyMap() - val UpdateAttrs: DomNodeWrapper.(Map) -> Unit = { - this.updateAttrs(it) + fun updateAttrs(attrs: Map) { + currentAttrs.forEach { + node.removeAttribute(it.key) } - val UpdateListeners: DomNodeWrapper.(List>) -> Unit = { - this.updateEventListeners(it) + currentAttrs = attrs + currentAttrs.forEach { + if (it.value != null) node.setAttribute(it.key, it.value ?: "") } - val UpdateProperties: DomNodePropertiesUpdater = { - this.updateProperties(it) + } + + fun updateProperties(list: List Unit, Any>>) { + if (node.className.isNotEmpty()) node.className = "" + + list.forEach { it.first(node, it.second) } + } + + fun updateStyleDeclarations(style: StyleHolder?) { + node.removeAttribute("style") + + style?.properties?.forEach { (name, value) -> + setProperty(node.attributeStyleMap, name, value) } - val UpdateStyleDeclarations: DomNodeWrapper.(StyleHolder?) -> Unit = { - this.updateStyleDeclarations(it) + style?.variables?.forEach { (name, value) -> + setVariable(node.style, name, value) } } -} - -typealias DomNodePropertiesUpdater = - DomNodeWrapper.(List Unit, Any>>) -> Unit \ No newline at end of file +} \ No newline at end of file diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt index 44c60053e6..c014326b67 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/RenderComposable.kt @@ -10,20 +10,20 @@ import kotlinx.browser.document import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.launch +import org.w3c.dom.Element import org.w3c.dom.HTMLBodyElement -import org.w3c.dom.HTMLElement import org.w3c.dom.get /** * Use this method to mount the composition at the certain [root] * - * @param root - the [HTMLElement] that will be the root of the DOM tree managed by Compose + * @param root - the [Element] that will be the root of the DOM tree managed by Compose * @param content - the Composable lambda that defines the composition content * * @return the instance of the [Composition] */ -fun renderComposable( - root: THTMLElement, - content: @Composable DOMScope.() -> Unit +fun renderComposable( + root: TElement, + content: @Composable DOMScope.() -> Unit ): Composition { GlobalSnapshotManager.ensureStarted() @@ -33,7 +33,7 @@ fun renderComposable( applier = DomApplier(DomNodeWrapper(root)), parent = recomposer ) - val scope = object : DOMScope {} + val scope = object : DOMScope {} composition.setContent @Composable { content(scope) } @@ -47,7 +47,7 @@ fun renderComposable( /** * Use this method to mount the composition at the element with id - [rootElementId]. * - * @param rootElementId - the id of the [HTMLElement] that will be the root of the DOM tree managed + * @param rootElementId - the id of the [Element] that will be the root of the DOM tree managed * by Compose * @param content - the Composable lambda that defines the composition content * @@ -56,9 +56,9 @@ fun renderComposable( @Suppress("UNCHECKED_CAST") fun renderComposable( rootElementId: String, - content: @Composable DOMScope.() -> Unit + content: @Composable DOMScope.() -> Unit ): Composition = renderComposable( - root = document.getElementById(rootElementId) as HTMLElement, + root = document.getElementById(rootElementId)!!, content = content ) diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt index d00e350d6e..82e85811dd 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt @@ -2,39 +2,39 @@ package androidx.compose.web.attributes import org.w3c.dom.HTMLInputElement -open class Tag { - object Div : Tag() - object A : Tag() - object Button : Tag() - object Form : Tag() - object Input : Tag() - object Select : Tag() - object Option : Tag() - object OptGroup : Tag() - object H : Tag() - object Ul : Tag() - object Ol : Tag() - object Li : Tag() - object Img : Tag() - object TextArea : Tag() - object Nav : Tag() - object Span : Tag() - object P : Tag() - object Br : Tag() - object Style : Tag() - object Pre : Tag() - object Code : Tag() - object Label : Tag() - object Table : Tag() - object Caption : Tag() - object Col : Tag() - object Colgroup : Tag() - object Tr : Tag() - object Thead : Tag() - object Th : Tag() - object Td : Tag() - object Tbody : Tag() - object Tfoot : Tag() +interface Tag { + object Div : Tag + object A : Tag + object Button : Tag + object Form : Tag + object Input : Tag + object Select : Tag + object Option : Tag + object OptGroup : Tag + object H : Tag + object Ul : Tag + object Ol : Tag + object Li : Tag + object Img : Tag + object TextArea : Tag + object Nav : Tag + object Span : Tag + object P : Tag + object Br : Tag + object Style : Tag + object Pre : Tag + object Code : Tag + object Label : Tag + object Table : Tag + object Caption : Tag + object Col : Tag + object Colgroup : Tag + object Tr : Tag + object Thead : Tag + object Th : Tag + object Td : Tag + object Tbody : Tag + object Tfoot : Tag } /* Anchor attributes */ diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt index 52ab4d87c5..d143992a29 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt @@ -4,14 +4,15 @@ import androidx.compose.runtime.DisposableEffectResult import androidx.compose.runtime.DisposableEffectScope import androidx.compose.web.css.StyleBuilder import androidx.compose.web.css.StyleBuilderImpl +import org.w3c.dom.Element import org.w3c.dom.HTMLElement class AttrsBuilder : EventsListenerBuilder() { private val attributesMap = mutableMapOf() val styleBuilder = StyleBuilderImpl() - val propertyUpdates = mutableListOf Unit, Any>>() - var refEffect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null + val propertyUpdates = mutableListOf Unit, Any>>() + var refEffect: (DisposableEffectScope.(Element) -> DisposableEffectResult)? = null fun style(builder: StyleBuilder.() -> Unit) { styleBuilder.apply(builder) @@ -32,7 +33,7 @@ class AttrsBuilder : EventsListenerBuilder() { fun tabIndex(value: Int) = attr(TAB_INDEX, value.toString()) fun spellCheck(value: Boolean) = attr(SPELLCHECK, value.toString()) - fun ref(effect: DisposableEffectScope.(HTMLElement) -> DisposableEffectResult) { + fun ref(effect: DisposableEffectScope.(Element) -> DisposableEffectResult) { this.refEffect = effect } @@ -48,7 +49,7 @@ class AttrsBuilder : EventsListenerBuilder() { @Suppress("UNCHECKED_CAST") fun prop(update: (E, V) -> Unit, value: V) { - propertyUpdates.add((update to value) as Pair<(HTMLElement, Any) -> Unit, Any>) + propertyUpdates.add((update to value) as Pair<(Element, Any) -> Unit, Any>) } fun collect(): Map { diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Base.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Base.kt index 2d4f4fed79..97a42ad322 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Base.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Base.kt @@ -11,12 +11,11 @@ import androidx.compose.runtime.SkippableUpdater import androidx.compose.runtime.currentComposer import androidx.compose.runtime.remember import androidx.compose.web.DomApplier -import androidx.compose.web.DomNodeWrapper +import androidx.compose.web.DomElementWrapper import androidx.compose.web.attributes.AttrsBuilder import androidx.compose.web.attributes.Tag -import androidx.compose.web.css.StyleBuilder -import androidx.compose.web.css.StyleBuilderImpl import kotlinx.browser.document +import org.w3c.dom.Element import org.w3c.dom.HTMLElement @OptIn(ComposeCompilerApi::class) @@ -47,22 +46,22 @@ inline fun > ComposeDomNode( } class DisposableEffectHolder( - var effect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null + var effect: (DisposableEffectScope.(Element) -> DisposableEffectResult)? = null ) @Composable -inline fun TagElement( +inline fun TagElement( tagName: String, crossinline applyAttrs: AttrsBuilder.() -> Unit, - content: @Composable ElementScope.() -> Unit + content: @Composable ElementScope.() -> Unit ) { - val scope = remember { ElementScopeImpl() } + val scope = remember { ElementScopeImpl() } val refEffect = remember { DisposableEffectHolder() } - ComposeDomNode, DomNodeWrapper, DomApplier>( + ComposeDomNode, DomElementWrapper, DomApplier>( factory = { - DomNodeWrapper(document.createElement(tagName)).also { - scope.element = it.node.unsafeCast() + DomElementWrapper(document.createElement(tagName) as HTMLElement).also { + scope.element = it.node.unsafeCast() } }, attrsSkippableUpdate = { @@ -72,10 +71,10 @@ inline fun TagElement( val events = attrsApplied.collectListeners() update { - set(attrsCollected, DomNodeWrapper.UpdateAttrs) - set(events, DomNodeWrapper.UpdateListeners) - set(attrsApplied.propertyUpdates, DomNodeWrapper.UpdateProperties) - set(attrsApplied.styleBuilder, DomNodeWrapper.UpdateStyleDeclarations) + set(attrsCollected, DomElementWrapper::updateAttrs) + set(events, DomElementWrapper::updateEventListeners) + set(attrsApplied.propertyUpdates, DomElementWrapper::updateProperties) + set(attrsApplied.styleBuilder, DomElementWrapper::updateStyleDeclarations) } }, elementScope = scope, diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/elements/ElementScope.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/elements/ElementScope.kt index b3dac921ac..bca92bc67c 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/elements/ElementScope.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/elements/ElementScope.kt @@ -10,38 +10,39 @@ import androidx.compose.runtime.RememberObserver import androidx.compose.runtime.SideEffect import androidx.compose.runtime.currentComposer import androidx.compose.runtime.remember +import org.w3c.dom.Element import org.w3c.dom.HTMLElement -interface DOMScope +interface DOMScope -interface ElementScope : DOMScope { +interface ElementScope : DOMScope { @Composable @NonRestartableComposable fun DisposableRefEffect( key: Any?, - effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult + effect: DisposableEffectScope.(TElement) -> DisposableEffectResult ) @Composable @NonRestartableComposable fun DisposableRefEffect( - effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult + effect: DisposableEffectScope.(TElement) -> DisposableEffectResult ) { DisposableRefEffect(null, effect) } @Composable @NonRestartableComposable - fun DomSideEffect(key: Any?, effect: DomEffectScope.(THTMLElement) -> Unit) + fun DomSideEffect(key: Any?, effect: DomEffectScope.(TElement) -> Unit) @Composable @NonRestartableComposable - fun DomSideEffect(effect: DomEffectScope.(THTMLElement) -> Unit) + fun DomSideEffect(effect: DomEffectScope.(TElement) -> Unit) } -abstract class ElementScopeBase : ElementScope { - abstract val element: THTMLElement +abstract class ElementScopeBase : ElementScope { + abstract val element: TElement private var nextDisposableDomEffectKey = 0 @@ -49,7 +50,7 @@ abstract class ElementScopeBase : ElementScope DisposableEffectResult + effect: DisposableEffectScope.(TElement) -> DisposableEffectResult ) { DisposableEffect(key) { effect(element) } } @@ -59,7 +60,7 @@ abstract class ElementScopeBase : ElementScope Unit + effect: DomEffectScope.(TElement) -> Unit ) { val changed = currentComposer.changed(key) val effectHolder = remember(key) { @@ -72,23 +73,23 @@ abstract class ElementScopeBase : ElementScope Unit) { + override fun DomSideEffect(effect: DomEffectScope.(TElement) -> Unit) { DomSideEffect(nextDisposableDomEffectKey++, effect) } } -open class ElementScopeImpl : ElementScopeBase() { - public override lateinit var element: THTMLElement +open class ElementScopeImpl : ElementScopeBase() { + public override lateinit var element: TElement } interface DomEffectScope { - fun onDispose(disposeEffect: (HTMLElement) -> Unit) + fun onDispose(disposeEffect: (Element) -> Unit) } private class DomDisposableEffectHolder( - val elementScope: ElementScopeBase + val elementScope: ElementScopeBase ) : RememberObserver, DomEffectScope { - var onDispose: ((HTMLElement) -> Unit)? = null + var onDispose: ((Element) -> Unit)? = null override fun onRemembered() {} @@ -98,7 +99,7 @@ private class DomDisposableEffectHolder( override fun onAbandoned() {} - override fun onDispose(disposeEffect: (HTMLElement) -> Unit) { + override fun onDispose(disposeEffect: (Element) -> Unit) { onDispose = disposeEffect } } \ No newline at end of file