CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-compose-ui--ui-wasm-js

Compose Multiplatform UI library for WebAssembly/JS target - declarative framework for sharing UIs across multiple platforms with Kotlin.

Pending
Overview
Eval results
Files

browser-integration.mddocs/

Browser Integration

Compose Multiplatform for WASM/JS provides seamless integration with browser APIs and web platform features. This enables access to browser-specific functionality, system preferences, network operations, and JavaScript interoperability while maintaining the declarative Compose programming model.

Browser API Access

Window and Document

Direct access to browser window and document objects.

// Browser window access
val window: Window = kotlinx.browser.window
val document: Document = kotlinx.browser.document

Window Operations:

@Composable
fun WindowIntegration() {
    LaunchedEffect(Unit) {
        // Window properties
        val windowWidth = window.innerWidth
        val windowHeight = window.innerHeight
        
        // Browser information
        val userAgent = window.navigator.userAgent
        val language = window.navigator.language
        val platform = window.navigator.platform
        
        // Location and history
        val currentUrl = window.location.href
        window.history.pushState(null, "New Page", "/new-path")
    }
}

Document Manipulation:

@Composable
fun DocumentIntegration() {
    LaunchedEffect(Unit) {
        // Document properties
        val title = document.title
        document.title = "New Title"
        
        // Element access
        val element = document.getElementById("myElement")
        val canvas = document.querySelector("canvas")
        
        // DOM manipulation
        val newElement = document.createElement("div")
        newElement.textContent = "Created from Kotlin"
        document.body?.appendChild(newElement)
    }
}

System Preferences Detection

Dark Mode Detection

fun isSystemInDarkTheme(): Boolean {
    return window.matchMedia("(prefers-color-scheme: dark)").matches
}

@Composable
fun DarkModeAwareTheme(content: @Composable () -> Unit) {
    var isDarkMode by remember { mutableStateOf(isSystemInDarkTheme()) }
    
    LaunchedEffect(Unit) {
        val mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
        val listener: (MediaQueryListEvent) -> Unit = { event ->
            isDarkMode = event.matches
        }
        
        mediaQuery.addEventListener("change", listener)
        // Cleanup handled automatically
    }
    
    MaterialTheme(
        colors = if (isDarkMode) darkColors() else lightColors(),
        content = content
    )
}

Language and Locale

fun getCurrentLanguage(): String {
    return window.navigator.languages.firstOrNull()
        ?: window.navigator.language
        ?: "en"
}

fun getCurrentLocale(): String {
    val language = getCurrentLanguage()
    return language.split("-").first() // Extract primary language
}

@Composable
fun LocalizedContent() {
    val currentLanguage = remember { getCurrentLanguage() }
    val isRightToLeft = remember { 
        listOf("ar", "he", "fa", "ur").contains(currentLanguage.split("-").first())
    }
    
    CompositionLocalProvider(
        LocalLayoutDirection provides if (isRightToLeft) LayoutDirection.Rtl else LayoutDirection.Ltr
    ) {
        // Content automatically adapts to language direction
        Text(stringResource(Res.string.welcome_message))
    }
}

Display and Accessibility

fun getDevicePixelRatio(): Double {
    return window.devicePixelRatio
}

fun prefersReducedMotion(): Boolean {
    return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}

fun prefersHighContrast(): Boolean {
    return window.matchMedia("(prefers-contrast: high)").matches
}

@Composable
fun AccessibilityAwareUI() {
    val reducedMotion = remember { prefersReducedMotion() }
    val highContrast = remember { prefersHighContrast() }
    
    AnimatedVisibility(
        visible = shouldShowContent,
        enter = if (reducedMotion) EnterTransition.None else fadeIn(),
        exit = if (reducedMotion) ExitTransition.None else fadeOut()
    ) {
        Surface(
            color = if (highContrast) Color.Black else MaterialTheme.colors.surface
        ) {
            // Content adapted for accessibility
        }
    }
}

Network Operations

Fetch API

Modern web API for network requests.

suspend fun fetchData(url: String): String {
    return window.fetch(url).await().text().await()
}

suspend fun fetchJson(url: String): dynamic {
    val response = window.fetch(url).await()
    if (!response.ok) {
        throw Exception("Network request failed: ${response.status}")
    }
    return response.json().await()
}

@Composable
fun NetworkDataLoader() {
    var data by remember { mutableStateOf<String?>(null) }
    var isLoading by remember { mutableStateOf(false) }
    var error by remember { mutableStateOf<String?>(null) }
    
    LaunchedEffect(Unit) {
        isLoading = true
        try {
            data = fetchData("https://api.example.com/data")
            error = null
        } catch (e: Exception) {
            error = e.message
            data = null
        } finally {
            isLoading = false
        }
    }
    
    when {
        isLoading -> CircularProgressIndicator()
        error != null -> Text("Error: $error", color = MaterialTheme.colors.error)
        data != null -> Text("Data: $data")
    }
}

XMLHttpRequest

Traditional HTTP requests with more control.

suspend fun xmlHttpRequest(url: String, method: String = "GET"): String {
    return suspendCoroutine { continuation ->
        val xhr = XMLHttpRequest()
        xhr.open(method, url)
        
        xhr.onload = {
            if (xhr.status == 200.toShort()) {
                continuation.resume(xhr.responseText)
            } else {
                continuation.resumeWithException(
                    Exception("HTTP ${xhr.status}: ${xhr.statusText}")
                )
            }
        }
        
        xhr.onerror = {
            continuation.resumeWithException(Exception("Network error"))
        }
        
        xhr.send()
    }
}

Local Storage

Browser Storage APIs

object BrowserStorage {
    fun setItem(key: String, value: String) {
        window.localStorage.setItem(key, value)
    }
    
    fun getItem(key: String): String? {
        return window.localStorage.getItem(key)
    }
    
    fun removeItem(key: String) {
        window.localStorage.removeItem(key)
    }
    
    fun clear() {
        window.localStorage.clear()
    }
}

@Composable
fun PersistentSettings() {
    var theme by remember { 
        val saved = BrowserStorage.getItem("theme") ?: "light"
        mutableStateOf(saved)
    }
    
    LaunchedEffect(theme) {
        BrowserStorage.setItem("theme", theme)
    }
    
    Switch(
        checked = theme == "dark",
        onCheckedChange = { isDark ->
            theme = if (isDark) "dark" else "light"
        }
    )
}

Session Storage

object SessionStorage {
    fun setItem(key: String, value: String) {
        window.sessionStorage.setItem(key, value)
    }
    
    fun getItem(key: String): String? {
        return window.sessionStorage.getItem(key)
    }
}

Platform Detection

Runtime Platform Information

fun getCurrentPlatform(): String = "Web WASM"

fun getBrowserInfo(): BrowserInfo {
    val userAgent = window.navigator.userAgent
    return BrowserInfo(
        name = detectBrowserName(userAgent),
        version = detectBrowserVersion(userAgent),
        isMobile = isMobileDevice(userAgent),
        supportsWebAssembly = supportsWasm(),
        supportsWebGL = supportsWebGL()
    )
}

data class BrowserInfo(
    val name: String,
    val version: String,
    val isMobile: Boolean,
    val supportsWebAssembly: Boolean,
    val supportsWebGL: Boolean
)

fun detectBrowserName(userAgent: String): String {
    return when {
        "Chrome" in userAgent -> "Chrome"
        "Firefox" in userAgent -> "Firefox"
        "Safari" in userAgent -> "Safari"
        "Edge" in userAgent -> "Edge"
        else -> "Unknown"
    }
}

@Composable
fun PlatformAwareUI() {
    val browserInfo = remember { getBrowserInfo() }
    
    if (browserInfo.isMobile) {
        // Mobile-optimized UI
        Column(
            modifier = Modifier.padding(8.dp)
        ) {
            // Larger touch targets
            Button(
                onClick = { /* action */ },
                modifier = Modifier
                    .fillMaxWidth()
                    .height(56.dp)
            ) {
                Text("Mobile Button")
            }
        }
    } else {
        // Desktop-optimized UI
        Row {
            // More compact layout
            Button(onClick = { /* action */ }) {
                Text("Desktop Button")
            }
        }
    }
}

JavaScript Interoperability

Calling JavaScript Functions

// Define external JavaScript functions
@JsFun("(message) => { console.log(message); }")
external fun jsConsoleLog(message: String)

@JsFun("(callback) => { setTimeout(callback, 1000); }")
external fun jsSetTimeout(callback: () -> Unit)

@JsFun("() => { return Date.now(); }")
external fun jsGetTimestamp(): Double

@Composable
fun JavaScriptIntegration() {
    LaunchedEffect(Unit) {
        // Call JavaScript functions
        jsConsoleLog("Hello from Kotlin!")
        
        val timestamp = jsGetTimestamp()
        println("Current timestamp: $timestamp")
        
        jsSetTimeout {
            println("JavaScript timeout callback executed")
        }
    }
}

Accessing JavaScript Objects

@Composable
fun JavaScriptObjectAccess() {
    LaunchedEffect(Unit) {
        // Access global JavaScript objects
        val console = window.asDynamic().console
        console.log("Direct console access")
        
        // Use browser APIs
        val performance = window.asDynamic().performance
        val navigationStart = performance.timing.navigationStart
        
        // Access custom JavaScript objects
        val customObject = window.asDynamic().myCustomObject
        if (customObject != null) {
            customObject.customMethod("parameter")
        }
    }
}

URL and Navigation

URL Management

@Composable
fun URLManagement() {
    var currentPath by remember { mutableStateOf(window.location.pathname) }
    
    LaunchedEffect(Unit) {
        // Listen for navigation events
        window.addEventListener("popstate") { event ->
            currentPath = window.location.pathname
        }
    }
    
    fun navigateTo(path: String) {
        window.history.pushState(null, "", path)
        currentPath = path
    }
    
    when (currentPath) {
        "/" -> HomeScreen()
        "/about" -> AboutScreen()
        "/settings" -> SettingsScreen()
        else -> NotFoundScreen()
    }
    
    BottomNavigation {
        BottomNavigationItem(
            icon = { Icon(Icons.Default.Home, contentDescription = null) },
            label = { Text("Home") },
            selected = currentPath == "/",
            onClick = { navigateTo("/") }
        )
        BottomNavigationItem(
            icon = { Icon(Icons.Default.Info, contentDescription = null) },
            label = { Text("About") },
            selected = currentPath == "/about",
            onClick = { navigateTo("/about") }
        )
    }
}

URL Parameters

fun parseUrlParameters(): Map<String, String> {
    val params = mutableMapOf<String, String>()
    val search = window.location.search.removePrefix("?")
    
    if (search.isNotEmpty()) {
        search.split("&").forEach { param ->
            val (key, value) = param.split("=", limit = 2)
            params[decodeURIComponent(key)] = decodeURIComponent(value)
        }
    }
    
    return params
}

@Composable
fun URLParameterHandler() {
    val urlParams = remember { parseUrlParameters() }
    val userId = urlParams["userId"]
    val tab = urlParams["tab"] ?: "profile"
    
    userId?.let { id ->
        UserProfile(userId = id, initialTab = tab)
    } ?: run {
        Text("User ID not provided")
    }
}

Performance Monitoring

Performance API

fun measurePerformance(): PerformanceData {
    val performance = window.asDynamic().performance
    val timing = performance.timing
    
    return PerformanceData(
        navigationStart = timing.navigationStart as Double,
        domContentLoaded = timing.domContentLoadedEventEnd as Double,
        loadComplete = timing.loadEventEnd as Double,
        firstPaint = getFirstPaintTime(),
        memory = getMemoryUsage()
    )
}

data class PerformanceData(
    val navigationStart: Double,
    val domContentLoaded: Double,
    val loadComplete: Double,
    val firstPaint: Double?,
    val memory: MemoryInfo?
)

@Composable
fun PerformanceMonitor() {
    var performanceData by remember { mutableStateOf<PerformanceData?>(null) }
    
    LaunchedEffect(Unit) {
        delay(1000) // Wait for page to load
        performanceData = measurePerformance()
    }
    
    performanceData?.let { data ->
        Column {
            Text("Load Time: ${data.loadComplete - data.navigationStart}ms")
            Text("DOM Ready: ${data.domContentLoaded - data.navigationStart}ms")
            data.memory?.let { memory ->
                Text("Memory Used: ${memory.usedJSHeapSize / 1024 / 1024}MB")
            }
        }
    }
}

Error Handling and Debugging

Browser Developer Tools

fun logToBrowserConsole(message: String, level: LogLevel = LogLevel.LOG) {
    val console = window.asDynamic().console
    when (level) {
        LogLevel.LOG -> console.log(message)
        LogLevel.INFO -> console.info(message)
        LogLevel.WARN -> console.warn(message)
        LogLevel.ERROR -> console.error(message)
    }
}

enum class LogLevel { LOG, INFO, WARN, ERROR }

@Composable
fun DebuggingSupport() {
    LaunchedEffect(Unit) {
        // Enable debugging in development
        if (isDebugMode()) {
            logToBrowserConsole("App started in debug mode", LogLevel.INFO)
            
            // Register global error handler
            window.addEventListener("error") { event ->
                val errorEvent = event as ErrorEvent
                logToBrowserConsole(
                    "Global error: ${errorEvent.message} at ${errorEvent.filename}:${errorEvent.lineno}",
                    LogLevel.ERROR
                )
            }
        }
    }
}

fun isDebugMode(): Boolean {
    return window.location.hostname == "localhost" ||
           window.location.search.contains("debug=true")
}

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-compose-ui--ui-wasm-js

docs

browser-integration.md

index.md

material-design.md

resource-management.md

state-management.md

ui-components.md

window-management.md

tile.json