From 7ea30be92404ef4dbdd679cca0b88970b6f3b12e Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Mon, 7 Feb 2022 15:27:48 +0100 Subject: [PATCH] web: hide internal properties from AttrsScope, EventsListenerScope, StyleScope (#1802) Co-authored-by: Oleksandr Karpovich --- .../compose/web/attributes/AttrsScope.kt | 53 +++---- .../web/attributes/EventsListenerScope.kt | 136 +++++++++--------- .../attributes/builders/InputAttrsScope.kt | 5 +- .../attributes/builders/SelectAttrsScope.kt | 4 +- .../attributes/builders/TextAreaAttrsScope.kt | 2 +- .../jetbrains/compose/web/css/StyleScope.kt | 21 +-- .../jetbrains/compose/web/elements/Base.kt | 5 +- .../compose/web/elements/Elements.kt | 14 +- .../jsTest/kotlin/elements/AttributesTests.kt | 7 +- web/settings.gradle.kts | 1 - 10 files changed, 121 insertions(+), 127 deletions(-) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsScope.kt index 81d6fd0f67..756853ce72 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsScope.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/AttrsScope.kt @@ -28,16 +28,6 @@ typealias AttrsBuilder = AttrsScopeBuilder * */ interface AttrsScope: EventsListenerScope { - @ComposeWebInternalApi - val attributesMap: Map - @ComposeWebInternalApi - val styleScope: StyleScope - - @ComposeWebInternalApi - val propertyUpdates: List 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: 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: EventsListenerScope { fun attr(attr: String, value: String): AttrsScope /** - * [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: EventsListenerScope { @Suppress("UNCHECKED_CAST") fun prop(update: (E, V) -> Unit, value: V) - @ComposeWebInternalApi - fun copyFrom(attrsScope: AttrsScope) - companion object { const val CLASS = "class" const val ID = "id" @@ -141,13 +126,13 @@ interface AttrsScope: EventsListenerScope { } } -open class AttrsScopeBuilder : AttrsScope, EventsListenerScope by EventsListenerScopeBuilder() { - override val attributesMap = mutableMapOf() - override val styleScope: StyleScope = StyleScopeBuilder() - - override val propertyUpdates = mutableListOf Unit, Any>>() - override var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null - +open class AttrsScopeBuilder( + internal val eventsListenerScopeBuilder: EventsListenerScopeBuilder = EventsListenerScopeBuilder() +) : AttrsScope, EventsListenerScope by eventsListenerScopeBuilder { + internal val attributesMap = mutableMapOf() + internal val styleScope: StyleScopeBuilder = StyleScopeBuilder() + internal val propertyUpdates = mutableListOf Unit, Any>>() + internal var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null internal val classes: MutableList = mutableListOf() /** @@ -160,6 +145,22 @@ open class AttrsScopeBuilder : AttrsScope, 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 : AttrsScope, EventsL } @ComposeWebInternalApi - override fun copyFrom(attrsScope: AttrsScope) { + internal fun copyFrom(attrsScope: AttrsScopeBuilder) { refEffect = attrsScope.refEffect styleScope.copyFrom(attrsScope.styleScope) attributesMap.putAll(attrsScope.attributesMap) propertyUpdates.addAll(attrsScope.propertyUpdates) - copyListenersFrom(attrsScope) + eventsListenerScopeBuilder.copyListenersFrom(attrsScope.eventsListenerScopeBuilder) } } diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerScope.kt index 65aa23d2b8..73b73f64ab 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerScope.kt +++ b/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> - @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) -> Unit) { registerEventListener(SyntheticEventListener(SCROLL, listener)) } - - 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 @@ -211,30 +206,27 @@ interface EventsListenerScope { ) { registerEventListener(SyntheticEventListener(eventName, listener)) } - + fun addEventListener( eventName: String, listener: (SyntheticEvent) -> 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> = mutableListOf() - +open class EventsListenerScopeBuilder : EventsListenerScope { + private val listeners: MutableList> = 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> = listeners } diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/InputAttrsScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/InputAttrsScope.kt index 5fc18e8a70..dfff085c00 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/InputAttrsScope.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/InputAttrsScope.kt @@ -38,8 +38,9 @@ typealias InputAttrsBuilder = InputAttrsScope * [onSelect] - add `select` event listener */ class InputAttrsScope( - val inputType: InputType -) : AttrsScopeBuilder() { + val inputType: InputType, + attrsScope: AttrsScope +) : AttrsScope by attrsScope { fun value(value: String): InputAttrsScope { when (inputType) { diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/SelectAttrsScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/SelectAttrsScope.kt index edce1c5b60..6e51dccfbd 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/SelectAttrsScope.kt +++ b/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() { +class SelectAttrsScope(attrsScope: AttrsScope) : AttrsScope by attrsScope { fun onInput( listener: (SyntheticInputEvent) -> Unit diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/TextAreaAttrsScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/TextAreaAttrsScope.kt index b6bbad3bb4..71084732b8 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/builders/TextAreaAttrsScope.kt +++ b/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() { +class TextAreaAttrsScope(attrsScope: AttrsScope) : AttrsScope by attrsScope { fun value(value: String): AttrsScope { prop(setInputValue, value) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleScope.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleScope.kt index 8d6a4d1edc..7107100e99 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleScope.kt +++ b/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) } diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt index 99c2fd96d5..d4d3f1484a 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Base.kt @@ -127,7 +127,10 @@ fun 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) } }, diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt index 0a4a820e94..719fb813e5 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/elements/Elements.kt +++ b/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 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 Input( } inputAttrsBuilder.attrs() - - this.copyFrom(inputAttrsBuilder) }, content = { if (type == InputType.Radio) { diff --git a/web/core/src/jsTest/kotlin/elements/AttributesTests.kt b/web/core/src/jsTest/kotlin/elements/AttributesTests.kt index cf848f8eaa..f3d722f543 100644 --- a/web/core/src/jsTest/kotlin/elements/AttributesTests.kt +++ b/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) } diff --git a/web/settings.gradle.kts b/web/settings.gradle.kts index bd278bba7f..39cd2ea0b4 100644 --- a/web/settings.gradle.kts +++ b/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")