All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.nacular.doodle.dom.SystemStyler.kt Maven / Gradle / Ivy

There is a newer version: 0.10.2
Show newest version
package io.nacular.doodle.dom

import io.nacular.doodle.dom.SystemStyler.Style
import io.nacular.doodle.utils.IdGenerator
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

/**
 * Created by Nicholas Eddy on 10/31/17.
 */
internal interface SystemStyler {
    interface Style {
        var css: String

        fun delete()
    }

    fun insertRule(css: String): Style?

    fun shutdown()
}

internal fun  cssStyle(initial: Style? = null): ReadWriteProperty = object:
    ReadWriteProperty {
    private var value = initial

    override fun getValue(thisRef: T, property: KProperty<*>) = value

    override fun setValue(thisRef: T, property: KProperty<*>, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") new: Style?) {
        if (new?.css != value?.css) {
            value?.delete()

            value = new
        }
    }
}

internal class SystemStylerImpl(
    htmlFactory: HtmlFactory,
    idGenerator: IdGenerator,
    private val document: Document,
    isNested: Boolean,
    allowDefaultDarkMode: Boolean
): SystemStyler {
    private val style: HTMLStyleElement = htmlFactory.create("style")

    private val meta: HTMLMetaElement?  = when(htmlFactory.root) {
        document.body -> htmlFactory.create("meta").apply {
            name    = "viewport"
            content = "width=device-width, initial-scale=1"
        }
        else -> null
    }

    private val id = when(htmlFactory.root) {
        document.body -> null
        else          -> ".${when (val i = htmlFactory.root.className) {
            "" -> idGenerator.nextId().also { htmlFactory.root.className = it }
            else      -> i
        }}"
    }

    private fun prefix(fallback: String = "") = id ?: fallback

    private val sheet: CSSStyleSheet?

    init {
        meta?.let { document.head?.insert(it, 0) }

        document.head?.insert(style, 0)

        sheet = style.sheet as? CSSStyleSheet?

        if (!isNested) {
            sheet?.apply {
                if (allowDefaultDarkMode) {
                    tryInsertRule(":root {color-scheme:light dark}", numStyles)
                }

                // Disable selection: https://stackoverflow.com/questions/826782/how-to-disable-text-selection-highlighting#4407335
                tryInsertRule("${prefix("body")} { -webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale }", numStyles)

                tryInsertRule("${prefix("html")} { border:0;box-sizing:border-box }", numStyles)
                tryInsertRule("${prefix("body")} { height:100%;width:100%;overflow:hidden;cursor:default;margin:0;padding:0;font-weight:$defaultFontWeight;font-family:$defaultFontFamily;font-size:${defaultFontSize}px }", numStyles)
                tryInsertRule("html { height:100%;width:100% }", numStyles)

                tryInsertRule("${prefix()} * { box-sizing:inherit }", numStyles)

                tryInsertRule("${prefix("body")} * { position:absolute;overflow:hidden;font-weight:$defaultFontSize;font-family:$defaultFontFamily;font-size:${defaultFontSize}px }", numStyles)
                tryInsertRule("${prefix("body")} pre { overflow:visible }", numStyles)
                tryInsertRule("${prefix("body")} :where(div) { display:inline }", numStyles)
                tryInsertRule("${prefix("body")} div:focus { outline:none }", numStyles)
                tryInsertRule("${prefix("body")} b { pointer-events:none }", numStyles)

                tryInsertRule("${prefix()} pre { margin:0;pointer-events:none }", numStyles)
                tryInsertRule("${prefix()} svg { display:inline-block;width:100%;height:100%;overflow:visible;pointer-events:none }", numStyles)
                tryInsertRule("${prefix()} svg * { position:absolute }", numStyles)
                tryInsertRule("${prefix()} svg foreignObject div { position:unset;display:block }", numStyles)
                tryInsertRule("${prefix()} button div svg { left:0px }", numStyles)

                tryInsertRule("input[type=text]::-ms-clear{ display:none }", numStyles)
            }
        }
    }

    private val ruleIndexes = mutableListOf()

    override fun insertRule(css: String): Style? = sheet?.run {
        try {
            val offset     = ruleIndexes.size
            val styleIndex = tryInsertRule(css, numStyles)

            if (styleIndex >= 0) {
                ruleIndexes += styleIndex

                val sheet = this

                cssRules.item(styleIndex)?.let { rule ->
                    object : Style {
                        override var css
                            get() = rule.cssText
                            set(new) {
                                rule.cssText = new
                            }

                        override fun delete() {
                            sheet.deleteRule(ruleIndexes[offset])

                            ruleIndexes[offset] = -1

                            for (i in offset + 1 until ruleIndexes.size) {
                                ruleIndexes[i] = ruleIndexes[i] - 1
                            }
                        }
                    }
                }
            } else null
        } catch (ignored: Throwable) { ignored.printStackTrace(); null }
    }

    override fun shutdown() {
        document.head?.remove(style)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy