Browse Source

web: hide internal properties from AttrsScope, EventsListenerScope, StyleScope (#1802)

Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
web_reuse_attrsScope_instance
Oleksandr Karpovich 2 years ago committed by GitHub
parent
commit
7ea30be924
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsScope.kt
  2. 136
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerScope.kt
  3. 5
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/InputAttrsScope.kt
  4. 4
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/SelectAttrsScope.kt
  5. 2
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/TextAreaAttrsScope.kt
  6. 21
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleScope.kt
  7. 5
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt
  8. 14
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt
  9. 7
      web/core/src/jsTest/kotlin/elements/AttributesTests.kt
  10. 1
      web/settings.gradle.kts

53
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsScope.kt

@ -28,16 +28,6 @@ typealias AttrsBuilder<T> = AttrsScopeBuilder<T>
*
*/
interface AttrsScope<TElement : Element>: EventsListenerScope {
@ComposeWebInternalApi
val attributesMap: Map<String, String>
@ComposeWebInternalApi
val styleScope: StyleScope
@ComposeWebInternalApi
val propertyUpdates: List<Pair<(Element, Any) -> Unit, Any>>
@ComposeWebInternalApi
var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)?
/**
* [style] add inline CSS-style properties to the element via [StyleScope] context
*
@ -50,9 +40,7 @@ interface AttrsScope<TElement : Element>: EventsListenerScope {
*
* `attr("style", ...)` overrides everything added in `style { }` blocks
*/
fun style(builder: StyleScope.() -> Unit) {
styleScope.apply(builder)
}
fun style(builder: StyleScope.() -> Unit)
/**
* [classes] adds all values passed as params to the element's classList.
@ -105,7 +93,7 @@ interface AttrsScope<TElement : Element>: EventsListenerScope {
fun attr(attr: String, value: String): AttrsScope<TElement>
/**
* [prop] allows setting values of element's properties which can't be set by ussing [attr].
* [prop] allows setting values of element's properties which can't be set using [attr].
* [update] is a lambda with two parameters: `element` and `value`. `element` is a reference to a native element.
* Some examples of properties that can set using [prop]: `value`, `checked`, `innerText`.
*
@ -124,9 +112,6 @@ interface AttrsScope<TElement : Element>: EventsListenerScope {
@Suppress("UNCHECKED_CAST")
fun <E : HTMLElement, V> prop(update: (E, V) -> Unit, value: V)
@ComposeWebInternalApi
fun copyFrom(attrsScope: AttrsScope<TElement>)
companion object {
const val CLASS = "class"
const val ID = "id"
@ -141,13 +126,13 @@ interface AttrsScope<TElement : Element>: EventsListenerScope {
}
}
open class AttrsScopeBuilder<TElement : Element> : AttrsScope<TElement>, EventsListenerScope by EventsListenerScopeBuilder() {
override val attributesMap = mutableMapOf<String, String>()
override val styleScope: StyleScope = StyleScopeBuilder()
override val propertyUpdates = mutableListOf<Pair<(Element, Any) -> Unit, Any>>()
override var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null
open class AttrsScopeBuilder<TElement : Element>(
internal val eventsListenerScopeBuilder: EventsListenerScopeBuilder = EventsListenerScopeBuilder()
) : AttrsScope<TElement>, EventsListenerScope by eventsListenerScopeBuilder {
internal val attributesMap = mutableMapOf<String, String>()
internal val styleScope: StyleScopeBuilder = StyleScopeBuilder()
internal val propertyUpdates = mutableListOf<Pair<(Element, Any) -> Unit, Any>>()
internal var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null
internal val classes: MutableList<String> = mutableListOf()
/**
@ -160,6 +145,22 @@ open class AttrsScopeBuilder<TElement : Element> : AttrsScope<TElement>, EventsL
this.classes.addAll(classes)
}
/**
* [style] add inline CSS-style properties to the element via [StyleScope] context
*
* Example:
* ```
* Div({
* style { maxWidth(5.px) }
* })
* ```
*
* `attr("style", ...)` overrides everything added in `style { }` blocks
*/
override fun style(builder: StyleScope.() -> Unit) {
styleScope.apply(builder)
}
/**
* [ref] can be used to retrieve a reference to a html element.
* The lambda that `ref` takes in is not Composable. It will be called only once when an element added into a composition.
@ -212,14 +213,14 @@ open class AttrsScopeBuilder<TElement : Element> : AttrsScope<TElement>, EventsL
}
@ComposeWebInternalApi
override fun copyFrom(attrsScope: AttrsScope<TElement>) {
internal fun copyFrom(attrsScope: AttrsScopeBuilder<TElement>) {
refEffect = attrsScope.refEffect
styleScope.copyFrom(attrsScope.styleScope)
attributesMap.putAll(attrsScope.attributesMap)
propertyUpdates.addAll(attrsScope.propertyUpdates)
copyListenersFrom(attrsScope)
eventsListenerScopeBuilder.copyListenersFrom(attrsScope.eventsListenerScopeBuilder)
}
}

136
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerScope.kt

@ -25,180 +25,175 @@ typealias EventsListenerBuilder = EventsListenerScopeBuilder
* use [addEventListener]
*/
interface EventsListenerScope {
@ComposeWebInternalApi
val listeners: List<SyntheticEventListener<*>>
@ComposeWebInternalApi
fun registerEventListener(listener: SyntheticEventListener<*>)
/* Mouse Events */
fun onContextMenu(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(CONTEXTMENU, listener))
}
fun onClick(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(CLICK, listener))
}
fun onDoubleClick(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(DBLCLICK, listener))
}
fun onMouseDown(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSEDOWN, listener))
}
fun onMouseUp(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSEUP, listener))
}
fun onMouseEnter(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSEENTER, listener))
}
fun onMouseLeave(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSELEAVE, listener))
}
fun onMouseMove(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSEMOVE, listener))
}
fun onMouseOut(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSEOUT, listener))
}
fun onMouseOver(listener: SyntheticMouseEventListener) {
registerEventListener(MouseEventListener(MOUSEOVER, listener))
}
fun onWheel(listener: SyntheticWheelEventListener) {
registerEventListener(MouseWheelEventListener(WHEEL, listener))
}
/* Drag Events */
fun onDrag(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DRAG, listener))
}
fun onDrop(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DROP, listener))
}
fun onDragStart(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DRAGSTART, listener))
}
fun onDragEnd(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DRAGEND, listener))
}
fun onDragOver(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DRAGOVER, listener))
}
fun onDragEnter(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DRAGENTER, listener))
}
fun onDragLeave(listener: SyntheticDragEventListener) {
registerEventListener(DragEventListener(DRAGLEAVE, listener))
}
/* End of Drag Events */
/* Clipboard Events */
fun onCopy(listener: (SyntheticClipboardEvent) -> Unit) {
registerEventListener(ClipboardEventListener(COPY, listener))
}
fun onCut(listener: (SyntheticClipboardEvent) -> Unit) {
registerEventListener(ClipboardEventListener(CUT, listener))
}
fun onPaste(listener: (SyntheticClipboardEvent) -> Unit) {
registerEventListener(ClipboardEventListener(PASTE, listener))
}
/* End of Clipboard Events */
/* Keyboard Events */
fun onKeyDown(listener: (SyntheticKeyboardEvent) -> Unit) {
registerEventListener(KeyboardEventListener(KEYDOWN, listener))
}
fun onKeyUp(listener: (SyntheticKeyboardEvent) -> Unit) {
registerEventListener(KeyboardEventListener(KEYUP, listener))
}
/* End of Keyboard Events */
/* Focus Events */
fun onFocus(listener: (SyntheticFocusEvent) -> Unit) {
registerEventListener(FocusEventListener(FOCUS, listener))
}
fun onBlur(listener: (SyntheticFocusEvent) -> Unit) {
registerEventListener(FocusEventListener(BLUR, listener))
}
fun onFocusIn(listener: (SyntheticFocusEvent) -> Unit) {
registerEventListener(FocusEventListener(FOCUSIN, listener))
}
fun onFocusOut(listener: (SyntheticFocusEvent) -> Unit) {
registerEventListener(FocusEventListener(FOCUSOUT, listener))
}
/* End of Focus Events */
/* Touch Events */
fun onTouchCancel(listener: (SyntheticTouchEvent) -> Unit) {
registerEventListener(TouchEventListener(TOUCHCANCEL, listener))
}
fun onTouchMove(listener: (SyntheticTouchEvent) -> Unit) {
registerEventListener(TouchEventListener(TOUCHMOVE, listener))
}
fun onTouchEnd(listener: (SyntheticTouchEvent) -> Unit) {
registerEventListener(TouchEventListener(TOUCHEND, listener))
}
fun onTouchStart(listener: (SyntheticTouchEvent) -> Unit) {
registerEventListener(TouchEventListener(TOUCHSTART, listener))
}
/* End of Touch Events */
/* Animation Events */
fun onAnimationEnd(listener: (SyntheticAnimationEvent) -> Unit) {
registerEventListener(AnimationEventListener(ANIMATIONEND, listener))
}
fun onAnimationIteration(listener: (SyntheticAnimationEvent) -> Unit) {
registerEventListener(AnimationEventListener(ANIMATIONITERATION, listener))
}
fun onAnimationStart(listener: (SyntheticAnimationEvent) -> Unit) {
registerEventListener(AnimationEventListener(ANIMATIONSTART, listener))
}
/* End of Animation Events */
fun onScroll(listener: (SyntheticEvent<EventTarget>) -> Unit) {
registerEventListener(SyntheticEventListener(SCROLL, listener))
}
fun collectListeners(): List<SyntheticEventListener<*>> = listeners
/**
* [addEventListener] used for adding arbitrary events to the element. It resembles the standard DOM addEventListener method
* @param eventName - the name of the event
@ -211,30 +206,27 @@ interface EventsListenerScope {
) {
registerEventListener(SyntheticEventListener(eventName, listener))
}
fun addEventListener(
eventName: String,
listener: (SyntheticEvent<EventTarget>) -> Unit
) {
registerEventListener(SyntheticEventListener(eventName, listener))
}
@ComposeWebInternalApi
fun copyListenersFrom(from: EventsListenerScope)
companion object {
const val COPY = "copy"
const val CUT = "cut"
const val PASTE = "paste"
const val CONTEXTMENU = "contextmenu"
const val CLICK = "click"
const val DBLCLICK = "dblclick"
const val FOCUS = "focus"
const val BLUR = "blur"
const val FOCUSIN = "focusin"
const val FOCUSOUT = "focusout"
const val KEYDOWN = "keydown"
const val KEYUP = "keyup"
const val MOUSEDOWN = "mousedown"
@ -247,22 +239,22 @@ interface EventsListenerScope {
const val WHEEL = "wheel"
const val SCROLL = "scroll"
const val SELECT = "select"
const val TOUCHCANCEL = "touchcancel"
const val TOUCHEND = "touchend"
const val TOUCHMOVE = "touchmove"
const val TOUCHSTART = "touchstart"
const val ANIMATIONCANCEL = "animationcancel" // firefox and safari only
const val ANIMATIONEND = "animationend"
const val ANIMATIONITERATION = "animationiteration"
const val ANIMATIONSTART = "animationstart"
const val BEFOREINPUT = "beforeinput"
const val INPUT = "input"
const val CHANGE = "change"
const val INVALID = "invalid"
const val DRAG = "drag"
const val DROP = "drop"
const val DRAGSTART = "dragstart"
@ -270,20 +262,22 @@ interface EventsListenerScope {
const val DRAGOVER = "dragover"
const val DRAGENTER = "dragenter"
const val DRAGLEAVE = "dragleave"
const val SUBMIT = "submit"
const val RESET = "reset"
}
}
open class EventsListenerScopeBuilder: EventsListenerScope {
override val listeners: MutableList<SyntheticEventListener<*>> = mutableListOf()
open class EventsListenerScopeBuilder : EventsListenerScope {
private val listeners: MutableList<SyntheticEventListener<*>> = mutableListOf()
override fun registerEventListener(listener: SyntheticEventListener<*>) {
listeners.add(listener)
}
override fun copyListenersFrom(from: EventsListenerScope) {
internal fun copyListenersFrom(from: EventsListenerScopeBuilder) {
listeners.addAll(from.listeners)
}
internal fun collectListeners(): List<SyntheticEventListener<*>> = listeners
}

5
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/InputAttrsScope.kt

@ -38,8 +38,9 @@ typealias InputAttrsBuilder<T> = InputAttrsScope<T>
* [onSelect] - add `select` event listener
*/
class InputAttrsScope<ValueType>(
val inputType: InputType<ValueType>
) : AttrsScopeBuilder<HTMLInputElement>() {
val inputType: InputType<ValueType>,
attrsScope: AttrsScope<HTMLInputElement>
) : AttrsScope<HTMLInputElement> by attrsScope {
fun value(value: String): InputAttrsScope<ValueType> {
when (inputType) {

4
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/SelectAttrsScope.kt

@ -5,7 +5,7 @@
package androidx.compose.web.attributes
import org.jetbrains.compose.web.attributes.AttrsScopeBuilder
import org.jetbrains.compose.web.attributes.AttrsScope
import org.jetbrains.compose.web.attributes.EventsListenerScope.Companion.CHANGE
import org.jetbrains.compose.web.attributes.EventsListenerScope.Companion.INPUT
import org.jetbrains.compose.web.attributes.SyntheticEventListener
@ -20,7 +20,7 @@ import org.w3c.dom.events.Event
)
typealias SelectAttrsBuilder = SelectAttrsScope
class SelectAttrsScope : AttrsScopeBuilder<HTMLSelectElement>() {
class SelectAttrsScope(attrsScope: AttrsScope<HTMLSelectElement>) : AttrsScope<HTMLSelectElement> by attrsScope {
fun onInput(
listener: (SyntheticInputEvent<String?, HTMLSelectElement>) -> Unit

2
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/TextAreaAttrsScope.kt

@ -26,7 +26,7 @@ import org.w3c.dom.HTMLTextAreaElement
)
typealias TextAreaAttrsBuilder = TextAreaAttrsScope
class TextAreaAttrsScope : AttrsScopeBuilder<HTMLTextAreaElement>() {
class TextAreaAttrsScope(attrsScope: AttrsScope<HTMLTextAreaElement>) : AttrsScope<HTMLTextAreaElement> by attrsScope {
fun value(value: String): AttrsScope<HTMLTextAreaElement> {
prop(setInputValue, value)

21
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleScope.kt

@ -10,13 +10,19 @@ package org.jetbrains.compose.web.css
import org.jetbrains.compose.web.internal.runtime.ComposeWebInternalApi
import kotlin.properties.ReadOnlyProperty
@Deprecated(
message = "Renamed to StyleScope",
replaceWith = ReplaceWith("StyleScope", "org.jetbrains.compose.web.css.StyleScope")
)
typealias StyleBuilder = StyleScope
/**
* StyleBuilder serves for two main purposes. Passed as a builder context (in [AttrsScope]), it
* StyleScope serves for two main purposes. Passed as a builder context (in [AttrsScope]), it
* makes it possible to:
* 1. Add inlined css properties to the element (@see [property])
* 2. Set values to CSS variables (@see [variable])
*/
interface StyleBuilder {
interface StyleScope {
/**
* Adds arbitrary CSS property to the inline style of the element
* @param propertyName - the name of css property as [per spec](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference)
@ -131,20 +137,14 @@ interface StyleHolder {
val variables: StylePropertyList
}
interface StyleScope : StyleBuilder, StyleHolder {
@ComposeWebInternalApi
fun copyFrom(sb: StyleScope)
}
@Deprecated(
message = "Renamed to StyleScopeBuilder",
replaceWith = ReplaceWith("StyleScopeBuilder", "org.jetbrains.compose.web.css.StyleScopeBuilder")
)
typealias StyleBuilderImpl = StyleScopeBuilder
@OptIn(ComposeWebInternalApi::class)
@Suppress("EqualsOrHashCode")
open class StyleScopeBuilder : StyleScope {
open class StyleScopeBuilder : StyleScope, StyleHolder {
override val properties: MutableStylePropertyList = mutableListOf()
override val variables: MutableStylePropertyList = mutableListOf()
@ -164,7 +164,8 @@ open class StyleScopeBuilder : StyleScope {
} else false
}
override fun copyFrom(sb: StyleScope) {
@ComposeWebInternalApi
internal fun copyFrom(sb: StyleHolder) {
properties.addAll(sb.properties)
variables.addAll(sb.variables)
}

5
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt

@ -127,7 +127,10 @@ fun <TElement : Element> TagElement(
set(attrsScope.classes, DomElementWrapper::updateClasses)
set(attrsScope.styleScope, DomElementWrapper::updateStyleDeclarations)
set(attrsScope.collect(), DomElementWrapper::updateAttrs)
set(attrsScope.collectListeners(), DomElementWrapper::updateEventListeners)
set(
attrsScope.eventsListenerScopeBuilder.collectListeners(),
DomElementWrapper::updateEventListeners
)
set(attrsScope.propertyUpdates, DomElementWrapper::updateProperties)
}
},

14
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt

@ -598,11 +598,7 @@ fun Select(
applyAttrs = {
if (multiple) multiple()
if (attrs != null) {
val selectAttrsBuilder = with(SelectAttrsScope()) {
attrs()
this
}
copyFrom(selectAttrsBuilder)
SelectAttrsScope(this).attrs()
}
},
content = content
@ -682,7 +678,7 @@ fun TextArea(
TagElement(
elementBuilder = TextArea,
applyAttrs = {
val textAreaAttrsBuilder = TextAreaAttrsScope()
val textAreaAttrsBuilder = TextAreaAttrsScope(this)
textAreaAttrsBuilder.onInput {
// controlled state needs to be restored after every input
keyForRestoringControlledState.value = keyForRestoringControlledState.value + 1
@ -693,8 +689,6 @@ fun TextArea(
if (firstProvidedValueWasNotNull) {
textAreaAttrsBuilder.value(value ?: "")
}
this.copyFrom(textAreaAttrsBuilder)
},
content = {
DisposableEffect(keyForRestoringControlledState.value) {
@ -1006,7 +1000,7 @@ fun <K> Input(
TagElement(
elementBuilder = Input,
applyAttrs = {
val inputAttrsBuilder = InputAttrsScope(type)
val inputAttrsBuilder = InputAttrsScope(type, this)
inputAttrsBuilder.type(type)
inputAttrsBuilder.onInput {
// controlled state needs to be restored after every input
@ -1014,8 +1008,6 @@ fun <K> Input(
}
inputAttrsBuilder.attrs()
this.copyFrom(inputAttrsBuilder)
},
content = {
if (type == InputType.Radio) {

7
web/core/src/jsTest/kotlin/elements/AttributesTests.kt

@ -164,7 +164,10 @@ class AttributesTests {
assertEquals(attrsScopeCopyFrom.styleScope, copyToAttrsScope.styleScope)
assertEquals(attrsScopeCopyFrom.refEffect, copyToAttrsScope.refEffect)
assertEquals(attrsScopeCopyFrom.propertyUpdates, copyToAttrsScope.propertyUpdates)
assertEquals(attrsScopeCopyFrom.collectListeners(), copyToAttrsScope.collectListeners())
assertEquals(
attrsScopeCopyFrom.eventsListenerScopeBuilder.collectListeners(),
copyToAttrsScope.eventsListenerScopeBuilder.collectListeners()
)
}
@Test
@ -186,7 +189,7 @@ class AttributesTests {
assertEquals("id1", copyToAttrsScope.attributesMap["id"])
assertEquals(StyleScopeBuilder().apply { width(100.px) }, copyToAttrsScope.styleScope)
val listeners = copyToAttrsScope.collectListeners()
val listeners = copyToAttrsScope.eventsListenerScopeBuilder.collectListeners()
assertEquals(1, listeners.size)
assertEquals("click", listeners[0].event)
}

1
web/settings.gradle.kts

@ -77,7 +77,6 @@ if (extra["compose.web.tests.skip.benchmarks"]!!.toString().toBoolean() != true)
if (extra["compose.web.buildSamples"]!!.toString().toBoolean() == true) {
println("building with examples")
module(":examples:falling-balls-web", "../examples/falling-balls-web")
module(":examples:compose-web-lp", "../examples/web-landing")
module(":examples:web-compose-bird", "../examples/web-compose-bird")
module(":examples:web-with-react", "../examples/web-with-react")

Loading…
Cancel
Save