diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsBuilder.kt index ec0a2c90e1..fe2ae3470c 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsBuilder.kt @@ -7,6 +7,16 @@ import org.jetbrains.compose.web.css.StyleBuilderImpl import org.w3c.dom.Element import org.w3c.dom.HTMLElement +/** + * [AttrsBuilder] is a class that is used (as a builder context, that is as AttrsBuilder.() -> Unit) + * in all DOM-element creating API calls. It's used for adding attributes to the element created, + * adding inline style values (via [style]) and attaching events to the element (since AttrsBuilder + * is an [EventsListenerBuilder]) + * + * In that aspect the most important method is [attr]. Setting the most frequently attributes, like [id], [tabIndex] + * are extracted to a separate methods. + * + */ open class AttrsBuilder : EventsListenerBuilder() { internal val attributesMap = mutableMapOf() val styleBuilder = StyleBuilderImpl() @@ -14,10 +24,26 @@ open class AttrsBuilder : EventsListenerBuilder() { val propertyUpdates = mutableListOf Unit, Any>>() var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null + /** + * [style] add inline CSS-style properties to the element via [StyleBuilder] context + * + * Example: + * ``` + * Div({ + * style { maxWidth(5.px) } + * }) + * ``` + */ fun style(builder: StyleBuilder.() -> Unit) { styleBuilder.apply(builder) } + /** + * [classes] adds all values passed as params to the element's classList. + * This method acts cumulatively, that is, each call adds values to the classList. + * In the ideology of Composable functions and their recomposition one just don't need to remove classes, + * since if your classList is, for instance, condition-dependent, you can always just call this method conditionally. + */ fun classes(vararg classes: String) = prop(setClassList, classes) fun id(value: String) = attr(ID, value) @@ -30,15 +56,48 @@ open class AttrsBuilder : EventsListenerBuilder() { fun tabIndex(value: Int) = attr(TAB_INDEX, value.toString()) fun spellCheck(value: Boolean) = attr(SPELLCHECK, value.toString()) + /** + * [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. + * Likewise, the lambda passed in `onDispose` will be called only once when an element leaves the composition. + * + * Under the hood, `ref` uses [DisposableEffect](https://developer.android.com/jetpack/compose/side-effects#disposableeffect) + */ fun ref(effect: DisposableEffectScope.(TElement) -> DisposableEffectResult) { this.refEffect = effect } + /** + * [attr] adds arbitrary attribute to the Element. + * If it called twice for the same attribute name, attribute value will be resolved to the last call. + * + * @param attr - the name of the attribute + * @param value - the value of the attribute + * + * For boolean attributes cast boolean value to String and pass it as value. + */ fun attr(attr: String, value: String): AttrsBuilder { attributesMap[attr] = value return this } + /** + * [prop] allows setting values of element's properties which can't be set by ussing [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`. + * + * Unlike [ref], lambda passed to [prop] will be invoked every time when AttrsBuilder being called during recomposition. + * [prop] is not supposed to be used for adding listeners, subscriptions, etc. + * Also see [ref]. + * + * Code Example: + * ``` + * Input(type = InputType.Text, attrs = { + * // This is only an example. One doesn't need to set `value` like this, since [Input] has `value(v: String)` + * prop({ element: HTMLInputElement, value: String -> element.value = value }, "someTextInputValue") + * }) + * ``` + */ @Suppress("UNCHECKED_CAST") fun prop(update: (E, V) -> Unit, value: V) { propertyUpdates.add((update to value) as Pair<(Element, Any) -> Unit, Any>) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt index 13fe73f02c..a0ddecdf6c 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt @@ -12,6 +12,12 @@ private typealias SyntheticMouseEventListener = (SyntheticMouseEvent) -> Unit private typealias SyntheticWheelEventListener = (SyntheticWheelEvent) -> Unit private typealias SyntheticDragEventListener = (SyntheticDragEvent) -> Unit +/** + * [EventsListenerBuilder] is used most often not directly but via [AttrsBuilder]. + * Its purpose is to add events to the element. For all most frequently used events there + * exist dedicated method. In case you need to support event that doesn't have such method, + * use [addEventListener] + */ open class EventsListenerBuilder { protected val listeners = mutableListOf>() @@ -184,6 +190,12 @@ open class EventsListenerBuilder { fun collectListeners(): List> = listeners + /** + * [addEventListener] used for adding arbitrary events to the element. It resembles the standard DOM addEventListener method + * @param eventName - the name of the event + * @param options - as of now this param is always equal to Options.DEFAULT + * @listener - event handler + */ fun > addEventListener( eventName: String, options: Options = Options.DEFAULT, diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt index 5d333ec1a2..4020e79e16 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleBuilder.kt @@ -9,7 +9,30 @@ package org.jetbrains.compose.web.css import kotlin.properties.ReadOnlyProperty +/** + * StyleBuilder serves for two main purposes. Passed as a builder context (in [AttrsBuilder]), 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 { + /** + * 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) + * @param value - the value, it can be either String or specialized type like [CSSNumeric] or [CSSColorValue] + * + * Most frequent CSS property values can be set via specialized methods, like [width], [display] etc. + * + * Example: + * ``` + * Div({ + * style { + * property("some-exotic-css-property", "I am a string value") + * property("some-exotic-css-property-width", 5.px) + * } + * }) + * ``` + */ fun property(propertyName: String, value: StylePropertyValue) fun variable(variableName: String, value: StylePropertyValue) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/ElementScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/ElementScope.kt index 1343bc5342..c13281068c 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/ElementScope.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/ElementScope.kt @@ -15,8 +15,27 @@ import org.w3c.dom.HTMLElement interface DOMScope +/** + * ElementScope allows adding effects to the Composable representing html element. + * Also see a tutorial: https://github.com/JetBrains/compose-jb/tree/master/tutorials/Web/Using_Effects + * + * Example: + * ``` + * Div { + * DisposableRefEffect { htmlDivElement -> + * onDispose {} + * } + * } + * ``` + */ interface ElementScope : DOMScope { + /** + * A side effect of composition that must run for any new unique value of [key] + * and must be reversed or cleaned up if [key] changes or if the DisposableRefEffect leaves the composition. + * [effect] lambda provides a reference to a native element represented by Composable. + * Adding [DisposableEffectScope.onDispose] to [effect] is mandatory. + */ @Composable @NonRestartableComposable fun DisposableRefEffect( @@ -24,6 +43,12 @@ interface ElementScope : DOMScope { effect: DisposableEffectScope.(TElement) -> DisposableEffectResult ) + /** + * A side effect of composition that must run once an element enters composition + * and must be reversed or cleaned up if element or the DisposableRefEffect leaves the composition. + * [effect] lambda provides a reference to a native element represented by Composable. + * Adding [DisposableEffectScope.onDispose] to [effect] is mandatory. + */ @Composable @NonRestartableComposable fun DisposableRefEffect( @@ -32,10 +57,20 @@ interface ElementScope : DOMScope { DisposableRefEffect(null, effect) } + /** + * A side effect of composition that runs on every successful recomposition if [key] changes. + * Also see [SideEffect]. + * Same as other effects in [ElementScope], it provides a reference to a native element in [effect] lambda. + */ @Composable @NonRestartableComposable fun DomSideEffect(key: Any?, effect: DomEffectScope.(TElement) -> Unit) + /** + * A side effect of composition that runs on every successful recomposition. + * Also see [SideEffect]. + * Same as other effects in [ElementScope], it provides a reference to a native element in [effect] lambda. + */ @Composable @NonRestartableComposable fun DomSideEffect(effect: DomEffectScope.(TElement) -> Unit) @@ -102,4 +137,4 @@ private class DomDisposableEffectHolder( override fun onDispose(disposeEffect: (Element) -> Unit) { onDispose = disposeEffect } -} \ No newline at end of file +}