mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
[web] Preprations for using DomApplier with Element
This commit is contained in:
@@ -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<WrappedEventListener<*>> = emptyList()
|
||||
private var currentAttrs: Map<String, String?> = emptyMap()
|
||||
|
||||
fun updateProperties(list: List<Pair<(HTMLElement, Any) -> 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<WrappedEventListener<*>>()
|
||||
|
||||
fun updateEventListeners(list: List<WrappedEventListener<*>>) {
|
||||
val htmlElement = node as? HTMLElement ?: return
|
||||
@@ -67,29 +58,6 @@ class DomNodeWrapper(val node: Node) {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAttrs(attrs: Map<String, String?>) {
|
||||
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 {
|
||||
|
||||
val UpdateAttrs: DomNodeWrapper.(Map<String, String?>) -> Unit = {
|
||||
this.updateAttrs(it)
|
||||
}
|
||||
val UpdateListeners: DomNodeWrapper.(List<WrappedEventListener<*>>) -> Unit = {
|
||||
this.updateEventListeners(it)
|
||||
}
|
||||
val UpdateProperties: DomNodePropertiesUpdater = {
|
||||
this.updateProperties(it)
|
||||
}
|
||||
val UpdateStyleDeclarations: DomNodeWrapper.(StyleHolder?) -> Unit = {
|
||||
this.updateStyleDeclarations(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias DomNodePropertiesUpdater =
|
||||
DomNodeWrapper.(List<Pair<(HTMLElement, Any) -> Unit, Any>>) -> Unit
|
||||
|
||||
class DomElementWrapper(override val node: HTMLElement): DomNodeWrapper(node) {
|
||||
private var currentAttrs = emptyMap<String, String?>()
|
||||
|
||||
fun updateAttrs(attrs: Map<String, String?>) {
|
||||
currentAttrs.forEach {
|
||||
node.removeAttribute(it.key)
|
||||
}
|
||||
currentAttrs = attrs
|
||||
currentAttrs.forEach {
|
||||
if (it.value != null) node.setAttribute(it.key, it.value ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateProperties(list: List<Pair<(Element, Any) -> 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)
|
||||
}
|
||||
style?.variables?.forEach { (name, value) ->
|
||||
setVariable(node.style, name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <THTMLElement : HTMLElement> renderComposable(
|
||||
root: THTMLElement,
|
||||
content: @Composable DOMScope<THTMLElement>.() -> Unit
|
||||
fun <TElement : Element> renderComposable(
|
||||
root: TElement,
|
||||
content: @Composable DOMScope<TElement>.() -> Unit
|
||||
): Composition {
|
||||
GlobalSnapshotManager.ensureStarted()
|
||||
|
||||
@@ -33,7 +33,7 @@ fun <THTMLElement : HTMLElement> renderComposable(
|
||||
applier = DomApplier(DomNodeWrapper(root)),
|
||||
parent = recomposer
|
||||
)
|
||||
val scope = object : DOMScope<THTMLElement> {}
|
||||
val scope = object : DOMScope<TElement> {}
|
||||
composition.setContent @Composable {
|
||||
content(scope)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ fun <THTMLElement : HTMLElement> 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 <THTMLElement : HTMLElement> renderComposable(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun renderComposable(
|
||||
rootElementId: String,
|
||||
content: @Composable DOMScope<HTMLElement>.() -> Unit
|
||||
content: @Composable DOMScope<Element>.() -> Unit
|
||||
): Composition = renderComposable(
|
||||
root = document.getElementById(rootElementId) as HTMLElement,
|
||||
root = document.getElementById(rootElementId)!!,
|
||||
content = content
|
||||
)
|
||||
|
||||
|
||||
@@ -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 <a> attributes */
|
||||
|
||||
@@ -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<TTag : Tag> : EventsListenerBuilder() {
|
||||
private val attributesMap = mutableMapOf<String, String>()
|
||||
val styleBuilder = StyleBuilderImpl()
|
||||
|
||||
val propertyUpdates = mutableListOf<Pair<(HTMLElement, Any) -> Unit, Any>>()
|
||||
var refEffect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null
|
||||
val propertyUpdates = mutableListOf<Pair<(Element, Any) -> Unit, Any>>()
|
||||
var refEffect: (DisposableEffectScope.(Element) -> DisposableEffectResult)? = null
|
||||
|
||||
fun style(builder: StyleBuilder.() -> Unit) {
|
||||
styleBuilder.apply(builder)
|
||||
@@ -32,7 +33,7 @@ class AttrsBuilder<TTag : Tag> : 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<TTag : Tag> : EventsListenerBuilder() {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <E : HTMLElement, V> 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<String, String> {
|
||||
|
||||
@@ -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 <TScope, T, reified E : Applier<*>> ComposeDomNode(
|
||||
}
|
||||
|
||||
class DisposableEffectHolder(
|
||||
var effect: (DisposableEffectScope.(HTMLElement) -> DisposableEffectResult)? = null
|
||||
var effect: (DisposableEffectScope.(Element) -> DisposableEffectResult)? = null
|
||||
)
|
||||
|
||||
@Composable
|
||||
inline fun <TTag : Tag, THTMLElement : HTMLElement> TagElement(
|
||||
inline fun <TTag : Tag, TElement : Element> TagElement(
|
||||
tagName: String,
|
||||
crossinline applyAttrs: AttrsBuilder<TTag>.() -> Unit,
|
||||
content: @Composable ElementScope<THTMLElement>.() -> Unit
|
||||
content: @Composable ElementScope<TElement>.() -> Unit
|
||||
) {
|
||||
val scope = remember { ElementScopeImpl<THTMLElement>() }
|
||||
val scope = remember { ElementScopeImpl<TElement>() }
|
||||
val refEffect = remember { DisposableEffectHolder() }
|
||||
|
||||
ComposeDomNode<ElementScope<THTMLElement>, DomNodeWrapper, DomApplier>(
|
||||
ComposeDomNode<ElementScope<TElement>, DomElementWrapper, DomApplier>(
|
||||
factory = {
|
||||
DomNodeWrapper(document.createElement(tagName)).also {
|
||||
scope.element = it.node.unsafeCast<THTMLElement>()
|
||||
DomElementWrapper(document.createElement(tagName) as HTMLElement).also {
|
||||
scope.element = it.node.unsafeCast<TElement>()
|
||||
}
|
||||
},
|
||||
attrsSkippableUpdate = {
|
||||
@@ -72,10 +71,10 @@ inline fun <TTag : Tag, THTMLElement : HTMLElement> 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,
|
||||
|
||||
@@ -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<out THTMLElement : HTMLElement>
|
||||
interface DOMScope<out TElement : Element>
|
||||
|
||||
interface ElementScope<out THTMLElement : HTMLElement> : DOMScope<THTMLElement> {
|
||||
interface ElementScope<out TElement : Element> : DOMScope<TElement> {
|
||||
|
||||
@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<out THTMLElement : HTMLElement> : ElementScope<THTMLElement> {
|
||||
abstract val element: THTMLElement
|
||||
abstract class ElementScopeBase<out TElement : Element> : ElementScope<TElement> {
|
||||
abstract val element: TElement
|
||||
|
||||
private var nextDisposableDomEffectKey = 0
|
||||
|
||||
@@ -49,7 +50,7 @@ abstract class ElementScopeBase<out THTMLElement : HTMLElement> : ElementScope<T
|
||||
@NonRestartableComposable
|
||||
override fun DisposableRefEffect(
|
||||
key: Any?,
|
||||
effect: DisposableEffectScope.(THTMLElement) -> DisposableEffectResult
|
||||
effect: DisposableEffectScope.(TElement) -> DisposableEffectResult
|
||||
) {
|
||||
DisposableEffect(key) { effect(element) }
|
||||
}
|
||||
@@ -59,7 +60,7 @@ abstract class ElementScopeBase<out THTMLElement : HTMLElement> : ElementScope<T
|
||||
@OptIn(ComposeCompilerApi::class)
|
||||
override fun DomSideEffect(
|
||||
key: Any?,
|
||||
effect: DomEffectScope.(THTMLElement) -> Unit
|
||||
effect: DomEffectScope.(TElement) -> Unit
|
||||
) {
|
||||
val changed = currentComposer.changed(key)
|
||||
val effectHolder = remember(key) {
|
||||
@@ -72,23 +73,23 @@ abstract class ElementScopeBase<out THTMLElement : HTMLElement> : ElementScope<T
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
override fun DomSideEffect(effect: DomEffectScope.(THTMLElement) -> Unit) {
|
||||
override fun DomSideEffect(effect: DomEffectScope.(TElement) -> Unit) {
|
||||
DomSideEffect(nextDisposableDomEffectKey++, effect)
|
||||
}
|
||||
}
|
||||
|
||||
open class ElementScopeImpl<THTMLElement : HTMLElement> : ElementScopeBase<THTMLElement>() {
|
||||
public override lateinit var element: THTMLElement
|
||||
open class ElementScopeImpl<TElement : Element> : ElementScopeBase<TElement>() {
|
||||
public override lateinit var element: TElement
|
||||
}
|
||||
|
||||
interface DomEffectScope {
|
||||
fun onDispose(disposeEffect: (HTMLElement) -> Unit)
|
||||
fun onDispose(disposeEffect: (Element) -> Unit)
|
||||
}
|
||||
|
||||
private class DomDisposableEffectHolder(
|
||||
val elementScope: ElementScopeBase<HTMLElement>
|
||||
val elementScope: ElementScopeBase<Element>
|
||||
) : 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user