From cc2209ce392c40e0fb7c4110a093bcf1e20360ab Mon Sep 17 00:00:00 2001 From: Shreck Ye Date: Wed, 11 Sep 2024 17:06:00 +0800 Subject: [PATCH 1/5] Use `ResizeObserver` to react to size changes on JS DOM in `BoxWithConstraints` See the added FIXME for the reason why it doesn't work --- .../layout/ext/BoxWithConstraints.js.kt | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/compose-multiplatform-common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt b/compose-multiplatform-common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt index b43dee99..1d7b5d57 100644 --- a/compose-multiplatform-common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt +++ b/compose-multiplatform-common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt @@ -7,6 +7,7 @@ import com.huanshankeji.compose.foundation.ExperimentalFoundationApi import com.huanshankeji.compose.foundation.layout.Box import com.huanshankeji.compose.ui.Alignment import com.huanshankeji.compose.ui.Modifier +import com.varabyte.kobweb.browser.dom.observers.ResizeObserver import com.varabyte.kobweb.compose.foundation.layout.BoxScope import com.varabyte.kobweb.compose.ui.attrsModifier @@ -19,14 +20,45 @@ actual fun BoxWithConstraints( ) { var clientSize by remember { mutableStateOf(null) } Box( - Modifier.platformModify { - attrsModifier { - ref { - clientSize = ClientSize(it.clientWidth, it.clientHeight) - onDispose { clientSize = null } + Modifier.fillMaxSizeStretch() + .platformModify { + attrsModifier { + ref { + //clientSize = ClientSize(it.clientWidth, it.clientHeight) // Adding this doesn't make a difference to solve the issue below. + val resizeObserver = ResizeObserver { entries, _ -> + val element = entries.first().target + + /* + console.log("width: ${element.clientWidth}, height: ${element.clientHeight}") + console.log(entries.first().contentBoxSize.first()) + console.log(entries.first().borderBoxSize.first()) + console.log(entries.first().devicePixelContentBoxSize.first()) + */ + + /* FIXME The height is sometimes 0 when resizing, + a non-zero size (as filtered through below) is not observed in time, + and sometimes a child element doesn't show, + until it's inspected with the Chrome Dev Tools. + I don't know whether this is a browser bug or a bug in our implementation. + Therefore, the 0 size changes are filtered out. + Uncomment the commented `console.log` debug code above to debug this further. */ + with(element) { + if (clientWidth != 0 && clientHeight != 0) { + //console.log("width: ${element.clientWidth}, height: ${element.clientHeight}") + clientSize = ClientSize(clientWidth, clientHeight) + } + } + } + resizeObserver.observe(it) + + onDispose { + //resizeObserver.unobserve(it) + resizeObserver.disconnect() + clientSize = null + } + } } } - }.fillMaxSizeStretch() .then(modifier), contentAlignment ) { From 171dd5be343610c749b653d94f186aca3ce850e5 Mon Sep 17 00:00:00 2001 From: Yongshun Shreck Ye Date: Tue, 10 Dec 2024 20:17:59 +0800 Subject: [PATCH 2/5] Update the project version for this branch and try replacing `Box` with `DivBox` in `BoxWithConstraints` --- buildSrc/src/main/kotlin/VersionsAndDependencies.kt | 2 +- .../compose/foundation/layout/ext/BoxWithConstraints.js.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/VersionsAndDependencies.kt b/buildSrc/src/main/kotlin/VersionsAndDependencies.kt index 7720bc25..ab751d73 100644 --- a/buildSrc/src/main/kotlin/VersionsAndDependencies.kt +++ b/buildSrc/src/main/kotlin/VersionsAndDependencies.kt @@ -1,7 +1,7 @@ import com.huanshankeji.CommonDependencies import org.jetbrains.compose.ComposeBuildConfig -val projectVersion = "0.5.1-SNAPSHOT" +val projectVersion = "0.5.1-box-with-constraints-react-to-size-changes-js-dom-SNAPSHOT" val commonDependencies = CommonDependencies() diff --git a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt index 1d7b5d57..f4c02c90 100644 --- a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt +++ b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt @@ -19,6 +19,7 @@ actual fun BoxWithConstraints( content: @Composable BoxWithConstraintsScope.() -> Unit ) { var clientSize by remember { mutableStateOf(null) } + // `DivBox` doesn't work here either, so it should not be Kobweb's problem. Box( Modifier.fillMaxSizeStretch() .platformModify { From fb269bdfa876bc63402c68a025325f42b8a8abec Mon Sep 17 00:00:00 2001 From: Yongshun Shreck Ye Date: Fri, 20 Dec 2024 19:00:18 +0800 Subject: [PATCH 3/5] Fix or try fixing JS DOM display issues in `BoxWithConstraints`, `com.huanshankeji.compose.material2.ext.TopAppBarScaffold`, and `com.huanshankeji.compose.material3.lazy.ext.List` 1. fix the bug that a direct child with `fillMaxSizeStretch` (CSS `stretch`) doesn't work properly in the `content` of `TopAppBarScaffold` that it's sometimes rendered with the height 0 This can be triggered either when used with `BoxWithConstraints` or by changing the window size, and the expected correct behavior can be restored by inspecting the element. The 0-height issue in `BoxWithConstraints` turns out to be caused by this. The root cause for this is that `fillMaxSizeStretch`/`stretch` seems buggy when used directly in a `position: absolute` parent. This may be a bug in the browser engines used. Consider reporting this. 1. try fixing an issue that a scrollable `List` with many items take all the space of a parent `Column` and hide components in the same `Column` before it, but fail --- .../huanshankeji/compose/foundation/Scroll.kt | 3 ++ .../compose/ui/unit/ext/DpOrPercentage.kt | 2 ++ .../compose/foundation/Scroll.js.kt | 1 - .../layout/ext/BoxWithConstraints.js.kt | 32 +++++++++++-------- .../material2/ext/TopAppBarScaffold.js.kt | 15 +++++++-- .../compose/material3/lazy/ext/List.js.kt | 6 ++++ 6 files changed, 41 insertions(+), 18 deletions(-) diff --git a/common/src/commonMain/kotlin/com/huanshankeji/compose/foundation/Scroll.kt b/common/src/commonMain/kotlin/com/huanshankeji/compose/foundation/Scroll.kt index fd772413..f54c430c 100644 --- a/common/src/commonMain/kotlin/com/huanshankeji/compose/foundation/Scroll.kt +++ b/common/src/commonMain/kotlin/com/huanshankeji/compose/foundation/Scroll.kt @@ -2,6 +2,7 @@ package com.huanshankeji.compose.foundation import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import com.huanshankeji.compose.ExperimentalApi import com.huanshankeji.compose.foundation.layout.BoxScope import com.huanshankeji.compose.ui.Alignment import com.huanshankeji.compose.ui.Modifier @@ -24,6 +25,7 @@ See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop. * it applies to the target as a whole on `androidx` targets, but applies to its content on JS DOM. * For consistency on different platforms, [VerticalScrollBox] is recommended over this modifier. */ +@ExperimentalApi expect fun Modifier.verticalScroll( state: ScrollState /* @@ -37,6 +39,7 @@ expect fun Modifier.verticalScroll( * For consistency on different platforms, [HorizontalScrollBox] is recommended over this modifier. * @see verticalScroll */ +@ExperimentalApi expect fun Modifier.horizontalScroll(state: ScrollState): Modifier @Composable diff --git a/common/src/commonMain/kotlin/com/huanshankeji/compose/ui/unit/ext/DpOrPercentage.kt b/common/src/commonMain/kotlin/com/huanshankeji/compose/ui/unit/ext/DpOrPercentage.kt index c8c64d7f..bf555d9c 100644 --- a/common/src/commonMain/kotlin/com/huanshankeji/compose/ui/unit/ext/DpOrPercentage.kt +++ b/common/src/commonMain/kotlin/com/huanshankeji/compose/ui/unit/ext/DpOrPercentage.kt @@ -1,8 +1,10 @@ package com.huanshankeji.compose.ui.unit.ext import androidx.annotation.IntRange +import com.huanshankeji.compose.ExperimentalApi // not used yet +@ExperimentalApi sealed class DpOrPercentage { class Dp(val dp: androidx.compose.ui.unit.Dp) : DpOrPercentage() class Percentage(@IntRange(0, 100) val percentage: Int) : DpOrPercentage() diff --git a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/Scroll.js.kt b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/Scroll.js.kt index 6ba45ff1..80a56aab 100644 --- a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/Scroll.js.kt +++ b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/Scroll.js.kt @@ -34,7 +34,6 @@ actual object ScrollState actual fun Modifier.verticalScroll(state: ScrollState): Modifier = platformModify { verticalScroll() } - actual fun Modifier.horizontalScroll(state: ScrollState): Modifier = platformModify { horizontalScroll() } diff --git a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt index f4c02c90..1a5c95e2 100644 --- a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt +++ b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt @@ -18,6 +18,7 @@ actual fun BoxWithConstraints( contentAlignment: Alignment, content: @Composable BoxWithConstraintsScope.() -> Unit ) { + // `DpClientSize.unspecified` instead of null can be used by default to prevent the content from not rendering when `clientSize` is not set var clientSize by remember { mutableStateOf(null) } // `DivBox` doesn't work here either, so it should not be Kobweb's problem. Box( @@ -25,29 +26,22 @@ actual fun BoxWithConstraints( .platformModify { attrsModifier { ref { - //clientSize = ClientSize(it.clientWidth, it.clientHeight) // Adding this doesn't make a difference to solve the issue below. + //console.log("Initial client size: ${it.clientWidth}, ${it.clientHeight}") + // Adding this doesn't make a difference in solving the issue below. + //clientSize = ClientSize(it.clientWidth, it.clientHeight) val resizeObserver = ResizeObserver { entries, _ -> - val element = entries.first().target + val element = entries.single().target /* console.log("width: ${element.clientWidth}, height: ${element.clientHeight}") console.log(entries.first().contentBoxSize.first()) console.log(entries.first().borderBoxSize.first()) - console.log(entries.first().devicePixelContentBoxSize.first()) + console.log(entries.first().devicePixelContentBoxSize.first()) // If there is zoom this one is different from the 2 above. */ - /* FIXME The height is sometimes 0 when resizing, - a non-zero size (as filtered through below) is not observed in time, - and sometimes a child element doesn't show, - until it's inspected with the Chrome Dev Tools. - I don't know whether this is a browser bug or a bug in our implementation. - Therefore, the 0 size changes are filtered out. - Uncomment the commented `console.log` debug code above to debug this further. */ with(element) { - if (clientWidth != 0 && clientHeight != 0) { - //console.log("width: ${element.clientWidth}, height: ${element.clientHeight}") - clientSize = ClientSize(clientWidth, clientHeight) - } + //console.log("width: $clientWidth, height: $clientHeight") + clientSize = ClientSize(clientWidth, clientHeight) } } resizeObserver.observe(it) @@ -78,3 +72,13 @@ class BoxWithConstraintsScopeImpl( ) : BoxWithConstraintsScope private class ClientSize(val clientWidth: Int, val clientHeight: Int) + +// removed if not used +/** + * Made in [Dp] so [Dp.Unspecified] can be used. + */ +private class DpClientSize(val clientWidth: Dp, val clientHeight: Dp) { + companion object { + val unspecified = DpClientSize(Dp.Unspecified, Dp.Unspecified) + } +} diff --git a/material2/src/jsMain/kotlin/com/huanshankeji/compose/material2/ext/TopAppBarScaffold.js.kt b/material2/src/jsMain/kotlin/com/huanshankeji/compose/material2/ext/TopAppBarScaffold.js.kt index 37852524..37e949aa 100644 --- a/material2/src/jsMain/kotlin/com/huanshankeji/compose/material2/ext/TopAppBarScaffold.js.kt +++ b/material2/src/jsMain/kotlin/com/huanshankeji/compose/material2/ext/TopAppBarScaffold.js.kt @@ -121,6 +121,8 @@ actual fun TopAppBarScaffold( actions, Modifier.weight(1f).fillMaxWidthStretch() ) { + // This part has a lot of nested `Div`s but works. Do not change unless you are sure that expected behavior is not broken. + // The content gets hidden behind the top app bar if this div is not added. Div({ style { @@ -134,9 +136,16 @@ actual fun TopAppBarScaffold( //overflow(Overflow.Auto) // This seems not needed. TODO remove if confirmed to be not needed } }) { - // see `ScaffoldLayoutWithMeasureFix` - val innerPadding = PaddingValues() - content(innerPadding) + // This nested `Div` is here so that a child using `fillMaxSizeStretch` works properly. `fillMaxSizeStretch` seems buggy when used directly in the `position: absolute` parent. + Div({ + style { + height(100.percent) + } + }) { + // see `ScaffoldLayoutWithMeasureFix` + val innerPadding = PaddingValues() + content(innerPadding) + } } floatingActionButton?.let { fabWithPosition(it) } diff --git a/material3/src/jsMain/kotlin/com/huanshankeji/compose/material3/lazy/ext/List.js.kt b/material3/src/jsMain/kotlin/com/huanshankeji/compose/material3/lazy/ext/List.js.kt index 1182d000..d1567b7e 100644 --- a/material3/src/jsMain/kotlin/com/huanshankeji/compose/material3/lazy/ext/List.js.kt +++ b/material3/src/jsMain/kotlin/com/huanshankeji/compose/material3/lazy/ext/List.js.kt @@ -90,6 +90,12 @@ actual class ListScope(val mdListScope: MdListScope) { actual class ItemScope(val mdListItemScope: MdListItemScope) +/* +@Composable +fun PrimitiveList() = + TODO() as Unit +*/ + @Composable actual fun List( modifier: Modifier, From f7dfe6dd5496bdcce8bf7029b0fdd4851e6fd3ae Mon Sep 17 00:00:00 2001 From: Yongshun Shreck Ye Date: Fri, 20 Dec 2024 19:06:30 +0800 Subject: [PATCH 4/5] Remove an additional comment related to removed code in the previous commit --- .../compose/foundation/layout/ext/BoxWithConstraints.js.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt index 1a5c95e2..d0807050 100644 --- a/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt +++ b/common/src/jsMain/kotlin/com/huanshankeji/compose/foundation/layout/ext/BoxWithConstraints.js.kt @@ -27,7 +27,6 @@ actual fun BoxWithConstraints( attrsModifier { ref { //console.log("Initial client size: ${it.clientWidth}, ${it.clientHeight}") - // Adding this doesn't make a difference in solving the issue below. //clientSize = ClientSize(it.clientWidth, it.clientHeight) val resizeObserver = ResizeObserver { entries, _ -> val element = entries.single().target From d9458497cf1fef7a2506a9d94e28b12b309f938d Mon Sep 17 00:00:00 2001 From: Yongshun Shreck Ye Date: Fri, 20 Dec 2024 19:17:29 +0800 Subject: [PATCH 5/5] Restore the project version --- buildSrc/src/main/kotlin/VersionsAndDependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/VersionsAndDependencies.kt b/buildSrc/src/main/kotlin/VersionsAndDependencies.kt index ab751d73..7720bc25 100644 --- a/buildSrc/src/main/kotlin/VersionsAndDependencies.kt +++ b/buildSrc/src/main/kotlin/VersionsAndDependencies.kt @@ -1,7 +1,7 @@ import com.huanshankeji.CommonDependencies import org.jetbrains.compose.ComposeBuildConfig -val projectVersion = "0.5.1-box-with-constraints-react-to-size-changes-js-dom-SNAPSHOT" +val projectVersion = "0.5.1-SNAPSHOT" val commonDependencies = CommonDependencies()