Mats-Hjalmar
01/04/2025, 1:56 PMMats-Hjalmar
01/04/2025, 1:58 PMimport androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateObserver
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusTarget
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.round
import kotlinx.browser.document
import org.w3c.dom.Document
import org.w3c.dom.Element
val NoOpUpdate: Element.() -> Unit = {}
private class ComponentInfo<T : Element> {
lateinit var container: Element
lateinit var component: T
lateinit var updater: Updater<T>
}
private class FocusSwitcher<T : Element>(
private val info: ComponentInfo<T>,
private val focusManager: FocusManager
) {
private val backwardRequester = FocusRequester()
private val forwardRequester = FocusRequester()
private var isRequesting = false
fun moveBackward() {
try {
isRequesting = true
backwardRequester.requestFocus()
} finally {
isRequesting = false
}
focusManager.moveFocus(FocusDirection.Previous)
}
fun moveForward() {
try {
isRequesting = true
forwardRequester.requestFocus()
} finally {
isRequesting = false
}
focusManager.moveFocus(FocusDirection.Next)
}
@Composable
fun Content() {
Box(
Modifier
.focusRequester(backwardRequester)
.onFocusChanged {
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)
val component = info.container.firstElementChild
if(component != null) {
requestFocus(component)
}else {
moveForward()
}
}
}
.focusTarget()
)
Box(
Modifier
.focusRequester(forwardRequester)
.onFocusChanged {
if (it.isFocused && !isRequesting) {
focusManager.clearFocus(force = true)
val component = info.container.lastElementChild
if(component != null) {
requestFocus(component)
}else {
moveBackward()
}
}
}
.focusTarget()
)
}
}
private fun requestFocus(element: Element) : Unit = js("""
{
element.focus();
}
""")
private fun initializingElement(element: Element) : Unit = js("""
{
element.style.position = 'absolute';
element.style.margin = '0px';
}
""")
private fun changeCoordinates(element: Element,width: Float,height: Float,x: Float,y: Float) : Unit = js("""
{
element.style.width = width + 'px';
element.style.height = height + 'px';
element.style.left = x + 'px';
<http://element.style.top|element.style.top> = y + 'px';
}
""")
@Composable
fun <T : Element> HtmlView(
factory: Document.() -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
) {
val componentInfo = remember { ComponentInfo<T>() }
val root = LocalLayerContainer.current
val density = LocalDensity.current.density
val focusManager = LocalFocusManager.current
val focusSwitcher = remember { FocusSwitcher(componentInfo, focusManager) }
Box(
modifier = modifier.onGloballyPositioned { coordinates ->
val location = coordinates.positionInWindow().round()
val size = coordinates.size
changeCoordinates(
componentInfo.component,
size.width / density,
size.height / density,
location.x / density,
location.y / density
)
}
) {
focusSwitcher.Content()
}
DisposableEffect(factory) {
componentInfo.container = document.createElement("div")
componentInfo.component = document.factory()
root.insertBefore(componentInfo.container, root.firstChild)
componentInfo.container.append(componentInfo.component)
componentInfo.updater = Updater(componentInfo.component, update)
initializingElement(componentInfo.component)
onDispose {
root.removeChild(componentInfo.container)
componentInfo.updater.dispose()
}
}
SideEffect {
componentInfo.updater.update = update
}
}
private class Updater<T : Element>(
private val component: T,
update: (T) -> Unit
) {
private var isDisposed = false
private val snapshotObserver = SnapshotStateObserver { command ->
command()
}
private val scheduleUpdate = { _: T ->
if(isDisposed.not()) {
performUpdate()
}
}
var update: (T) -> Unit = update
set(value) {
if (field != value) {
field = value
performUpdate()
}
}
private fun performUpdate() {
snapshotObserver.observeReads(component, scheduleUpdate) {
update(component)
}
}
init {
snapshotObserver.start()
performUpdate()
}
fun dispose() {
snapshotObserver.stop()
snapshotObserver.clear()
isDisposed = true
}
}
val LocalLayerContainer = staticCompositionLocalOf<Element> {
document.body ?:
error("CompositionLocal LayerContainer not provided")
// you can replace this with document.body!!
}
This is the code i am using to draw html elementsFrançois
01/04/2025, 2:33 PMMats-Hjalmar
01/04/2025, 2:44 PMSargun Vohra
01/04/2025, 6:27 PMMats-Hjalmar
01/04/2025, 6:42 PMWebAssembly.instantiate(): Import #4924 "./skiko.mjs" "org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect": function import requires a callable
LinkError: WebAssembly.instantiate(): Import #4924 "./skiko.mjs" "org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect": function import requires a callable
Note: I have removed the CompositionLocalProvider
CompositionLocalProvider(LocalLayerContainer provides document.body!!) {
App()
}
Mats-Hjalmar
01/04/2025, 6:43 PMSargun Vohra
01/04/2025, 6:43 PMMats-Hjalmar
01/04/2025, 6:44 PMMats-Hjalmar
01/04/2025, 6:45 PMHtmlElement(
modifier = modifier,
zIndex = "0",
factory = {
playerProvider.player.apply {
controls = showControls
}
}
)
Mats-Hjalmar
01/04/2025, 6:49 PMHtmlView
and i can’t see a connection between that code and the error.Sargun Vohra
01/04/2025, 6:53 PMMats-Hjalmar
01/04/2025, 7:09 PMMats-Hjalmar
01/04/2025, 7:11 PMprivate fun initializingElement(element: Element, zIndex: String) : Unit = js(""" {
element.style.position = 'absolute';
element.style.margin = '0px';
element.style.zIndex = zIndex;
}
""")
Mats-Hjalmar
01/04/2025, 7:14 PMSargun Vohra
01/04/2025, 7:14 PMMats-Hjalmar
01/04/2025, 7:16 PMMats-Hjalmar
01/04/2025, 7:19 PMSargun Vohra
01/04/2025, 7:22 PMjs
block. The Kotlin compiler just sees that JS code as a string and doesn't know you've used the parameter.
For zIndex, you're using a template string to put the .toString() of the variable directly into the JS code (which doesn't work). If you didn't use a template string, zIndex would appear unused too.Mats-Hjalmar
01/04/2025, 7:23 PMSargun Vohra
01/04/2025, 7:24 PMelement.style.zIndex = zIndex
should work, without the templatingMats-Hjalmar
01/04/2025, 7:26 PM