You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

244 lines
12 KiB

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 org.jetbrains.compose.web.DomApplier
import org.jetbrains.compose.web.DomElementWrapper
import org.jetbrains.compose.web.attributes.AttrsBuilder
import kotlinx.browser.document
import org.w3c.dom.Audio
import org.w3c.dom.Element
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLAreaElement
import org.w3c.dom.HTMLAudioElement
import org.w3c.dom.HTMLBRElement
import org.w3c.dom.HTMLButtonElement
import org.w3c.dom.HTMLDataListElement
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLEmbedElement
import org.w3c.dom.HTMLFieldSetElement
import org.w3c.dom.HTMLFormElement
import org.w3c.dom.HTMLHRElement
import org.w3c.dom.HTMLHeadingElement
import org.w3c.dom.HTMLIFrameElement
import org.w3c.dom.HTMLImageElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLLIElement
import org.w3c.dom.HTMLLabelElement
import org.w3c.dom.HTMLLegendElement
import org.w3c.dom.HTMLMapElement
import org.w3c.dom.HTMLMeterElement
import org.w3c.dom.HTMLOListElement
import org.w3c.dom.HTMLObjectElement
import org.w3c.dom.HTMLOptGroupElement
import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.HTMLOutputElement
import org.w3c.dom.HTMLParagraphElement
import org.w3c.dom.HTMLParamElement
import org.w3c.dom.HTMLPictureElement
import org.w3c.dom.HTMLPreElement
import org.w3c.dom.HTMLProgressElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.HTMLSourceElement
import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.HTMLStyleElement
import org.w3c.dom.HTMLTableCaptionElement
import org.w3c.dom.HTMLTableCellElement
import org.w3c.dom.HTMLTableColElement
import org.w3c.dom.HTMLTableElement
import org.w3c.dom.HTMLTableRowElement
import org.w3c.dom.HTMLTableSectionElement
import org.w3c.dom.HTMLTextAreaElement
import org.w3c.dom.HTMLTrackElement
import org.w3c.dom.HTMLUListElement
import org.w3c.dom.HTMLVideoElement
@OptIn(ComposeCompilerApi::class)
@Composable
@ExplicitGroupsComposable
inline fun <TScope, T, reified E : Applier<*>> 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)
} else {
currentComposer.useNode()
}
SkippableUpdater<T>(currentComposer).apply {
attrsSkippableUpdate()
}
currentComposer.startReplaceableGroup(0x7ab4aae9)
content?.invoke(elementScope)
currentComposer.endReplaceableGroup()
currentComposer.endNode()
}
class DisposableEffectHolder(
var effect: (DisposableEffectScope.(Element) -> DisposableEffectResult)? = null
)
interface ElementBuilder<TElement : Element> {
fun create(): TElement
private open class ElementBuilderImplementation<TElement : Element>(private val tagName: String) : ElementBuilder<TElement> {
private val el: Element by lazy { document.createElement(tagName) }
override fun create(): TElement = el.cloneNode() as TElement
}
companion object {
fun <TElement : Element> createBuilder(tagName: String): ElementBuilder<TElement> {
return object : ElementBuilderImplementation<TElement>(tagName) {}
}
val Address: ElementBuilder<HTMLElement> = ElementBuilderImplementation("address")
val Article: ElementBuilder<HTMLElement> = ElementBuilderImplementation("article")
val Aside: ElementBuilder<HTMLElement> = ElementBuilderImplementation("aside")
val Header: ElementBuilder<HTMLElement> = ElementBuilderImplementation("header")
val Area: ElementBuilder<HTMLAreaElement> = ElementBuilderImplementation("area")
val Audio: ElementBuilder<HTMLAudioElement> = ElementBuilderImplementation("audio")
val Map: ElementBuilder<HTMLMapElement> = ElementBuilderImplementation("map")
val Track: ElementBuilder<HTMLTrackElement> = ElementBuilderImplementation("track")
val Video: ElementBuilder<HTMLVideoElement> = ElementBuilderImplementation("video")
val Datalist: ElementBuilder<HTMLDataListElement> = ElementBuilderImplementation("datalist")
val Fieldset: ElementBuilder<HTMLFieldSetElement> = ElementBuilderImplementation("fieldset")
val Legend: ElementBuilder<HTMLLegendElement> = ElementBuilderImplementation("legend")
val Meter: ElementBuilder<HTMLMeterElement> = ElementBuilderImplementation("meter")
val Output: ElementBuilder<HTMLOutputElement> = ElementBuilderImplementation("output")
val Progress: ElementBuilder<HTMLProgressElement> = ElementBuilderImplementation("progress")
val Embed: ElementBuilder<HTMLEmbedElement> = ElementBuilderImplementation("embed")
val Iframe: ElementBuilder<HTMLIFrameElement> = ElementBuilderImplementation("iframe")
val Object: ElementBuilder<HTMLObjectElement> = ElementBuilderImplementation("object")
val Param: ElementBuilder<HTMLParamElement> = ElementBuilderImplementation("param")
val Picture: ElementBuilder<HTMLPictureElement> = ElementBuilderImplementation("picture")
val Source: ElementBuilder<HTMLSourceElement> = ElementBuilderImplementation("source")
val Div: ElementBuilder<HTMLDivElement> = ElementBuilderImplementation("div")
val A: ElementBuilder<HTMLAnchorElement> = ElementBuilderImplementation("a")
val Input: ElementBuilder<HTMLInputElement> = ElementBuilderImplementation("input")
val Button: ElementBuilder<HTMLButtonElement> = ElementBuilderImplementation("button")
val H1: ElementBuilder<HTMLHeadingElement> = ElementBuilderImplementation("h1")
val H2: ElementBuilder<HTMLHeadingElement> = ElementBuilderImplementation("h2")
val H3: ElementBuilder<HTMLHeadingElement> = ElementBuilderImplementation("h3")
val H4: ElementBuilder<HTMLHeadingElement> = ElementBuilderImplementation("h4")
val H5: ElementBuilder<HTMLHeadingElement> = ElementBuilderImplementation("h5")
val H6: ElementBuilder<HTMLHeadingElement> = ElementBuilderImplementation("h6")
val P: ElementBuilder<HTMLParagraphElement> = ElementBuilderImplementation<HTMLParagraphElement>("p")
val Em: ElementBuilder<HTMLElement> = ElementBuilderImplementation("em")
val I: ElementBuilder<HTMLElement> = ElementBuilderImplementation("i")
val B: ElementBuilder<HTMLElement> = ElementBuilderImplementation("b")
val Small: ElementBuilder<HTMLElement> = ElementBuilderImplementation("small")
val Span: ElementBuilder<HTMLSpanElement> = ElementBuilderImplementation("span")
val Br: ElementBuilder<HTMLBRElement> = ElementBuilderImplementation("br")
val Ul: ElementBuilder<HTMLUListElement> = ElementBuilderImplementation("ul")
val Ol: ElementBuilder<HTMLOListElement> = ElementBuilderImplementation("ol")
val Li: ElementBuilder<HTMLLIElement> = ElementBuilderImplementation("li")
val Img: ElementBuilder<HTMLImageElement> = ElementBuilderImplementation("img")
val Form: ElementBuilder<HTMLFormElement> = ElementBuilderImplementation("form")
val Select: ElementBuilder<HTMLSelectElement> = ElementBuilderImplementation("select")
val Option: ElementBuilder<HTMLOptionElement> = ElementBuilderImplementation("option")
val OptGroup: ElementBuilder<HTMLOptGroupElement> = ElementBuilderImplementation("optgroup")
val Section: ElementBuilder<HTMLElement> = ElementBuilderImplementation("section")
val TextArea: ElementBuilder<HTMLTextAreaElement> = ElementBuilderImplementation("textarea")
val Nav: ElementBuilder<HTMLElement> = ElementBuilderImplementation("nav")
val Pre: ElementBuilder<HTMLPreElement> = ElementBuilderImplementation("pre")
val Code: ElementBuilder<HTMLElement> = ElementBuilderImplementation("code")
val Main: ElementBuilder<HTMLElement> = ElementBuilderImplementation("main")
val Footer: ElementBuilder<HTMLElement> = ElementBuilderImplementation("footer")
val Hr: ElementBuilder<HTMLHRElement> = ElementBuilderImplementation("hr")
val Label: ElementBuilder<HTMLLabelElement> = ElementBuilderImplementation("label")
val Table: ElementBuilder<HTMLTableElement> = ElementBuilderImplementation("table")
val Caption: ElementBuilder<HTMLTableCaptionElement> = ElementBuilderImplementation("caption")
val Col: ElementBuilder<HTMLTableColElement> = ElementBuilderImplementation("col")
val Colgroup: ElementBuilder<HTMLTableColElement> = ElementBuilderImplementation("colgroup")
val Tr: ElementBuilder<HTMLTableRowElement> = ElementBuilderImplementation("tr")
val Thead: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("thead")
val Th: ElementBuilder<HTMLTableCellElement> = ElementBuilderImplementation("th")
val Td: ElementBuilder<HTMLTableCellElement> = ElementBuilderImplementation("td")
val Tbody: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("tbody")
val Tfoot: ElementBuilder<HTMLTableSectionElement> = ElementBuilderImplementation("tfoot")
val Style: ElementBuilder<HTMLStyleElement> = ElementBuilderImplementation("style")
}
}
@Composable
fun <TElement : Element> TagElement(
elementBuilder: ElementBuilder<TElement>,
applyAttrs: (AttrsBuilder<TElement>.() -> Unit)?,
content: (@Composable ElementScope<TElement>.() -> Unit)?
) {
val scope = remember { ElementScopeImpl<TElement>() }
val refEffect = remember { DisposableEffectHolder() }
ComposeDomNode<ElementScope<TElement>, DomElementWrapper, DomApplier>(
factory = {
DomElementWrapper(elementBuilder.create() as HTMLElement).also {
scope.element = it.node.unsafeCast<TElement>()
}
},
attrsSkippableUpdate = {
val attrsApplied = AttrsBuilder<TElement>().also {
if (applyAttrs != null) {
it.applyAttrs()
}
}
refEffect.effect = attrsApplied.refEffect
val attrsCollected = attrsApplied.collect()
val events = attrsApplied.collectListeners()
update {
set(attrsCollected, DomElementWrapper::updateAttrs)
set(events, DomElementWrapper::updateEventListeners)
set(attrsApplied.propertyUpdates, DomElementWrapper::updateProperties)
set(attrsApplied.styleBuilder, DomElementWrapper::updateStyleDeclarations)
}
},
elementScope = scope,
content = content
)
DisposableEffect(null) {
refEffect.effect?.invoke(this, scope.element) ?: onDispose {}
}
}
@Composable
fun <TElement : Element> TagElement(
tagName: String,
applyAttrs: AttrsBuilder<TElement>.() -> Unit,
content: (@Composable ElementScope<TElement>.() -> Unit)?
) = TagElement(
elementBuilder = ElementBuilder.createBuilder(tagName),
applyAttrs = applyAttrs,
content = content
)