varabyte / kobweb

A modern framework for full stack web apps in Kotlin, built upon Compose HTML
https://kobweb.varabyte.com
Apache License 2.0
1.53k stars 68 forks source link

Add the `wrapContent...` modifiers #512

Closed ShreckYe closed 4 months ago

ShreckYe commented 5 months ago

I'd like to propose these modifiers that are more akin to those in Compose.

ShreckYe commented 5 months ago

I am actually not very sure about whether the Compose wrapContent... behaves the same as the CSS fit-content either. I have been developing a project that delegates to both androidx.compose and Compose HTML that also depends on your work, in which as I tested they behave similarly in the way that they shrink a layout to its contents, but I do think that there are some differences.

From the wrapContentWidth docs:

Allow the content to measure at its desired width without regard for the incoming measurement [minimum width constraint](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints#minWidth()), and, if unbounded is true, also without regard for the incoming measurement [maximum width constraint](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints#maxWidth()). If the content's measured size is smaller than the minimum width constraint, align it within that minimum width space. If the content's measured size is larger than the maximum width constraint (only possible when unbounded is true), align over the maximum width space.

From this Stack Overflow answer:

fit-content uses max-content, unless available < max-content, then it uses available. Unless available < min-content, then it uses min-content.

So as I see from the docs they are quite different. It seems to me that Jetpack Compose follows a top-down approach where it passes size constraints from top to bottom, and HTML rendering by default follows a bottom-up approach where children/content sizes are determined before those of parents/containers. I think this is due to that Compose inherently renders on a bounded screen, while HTML inherently renders on unbounded space enabled by scrollbars. And as stated in the docs modifiers act as chained wrappers that wrap the component, while CSS styles act as properties of the component itself. In the case of wrapContentWidth and fit-content, when there isn't a passed minimum width constraint from the parent on Compose as in most cases, wrapContentSize will make the containing layout the size of min(content, constraint_max), which is just similar to min(max-content, available) for fit-content; but in the case when the content is bigger than the containing layout and especially when unbouned is true, Compose only shows part of the content (by default at the center), but the browser expands the containing layout and thus the whole webpage, adding scrollbars when the body exceeds the browser window. As it seems to me the Compose behavior is similar to setting CSS overflow.

However, there are also similar inconsistencies with Box, Row, Column, and fillMaxWidth/Height/Size which are not that obvious. As I tested in my project the Box composable (and Row and Column) seems to shrink to its content size on Compose but the Kobweb Box doesn't, and the fillMaxWidth/Height/Size modifier inflates the containing layout to the constraint max while the Kobweb one doesn't. And as Compose and HTML are rendered differently internally there may not be a perfect answer. This may also be related to block and inline HTML elements.

I'm worried that the Jetpack Compose methods here are fundamentally different than the CSS approach we're replacing them with. If we get this wrong, then we'll be giving users the wrong mental model, and worst case people who find the behavior different from what they expected will file bugs, at which point we might not be able to change things easily due to legacy code.

So I do agree with you on this and I am no expert on browser engines either. Maybe we should wait for someone who's more specialized in both Compose and browser rendering to comment on this.

ShreckYe commented 5 months ago

This question android - Match Width of Parent in Column (Jetpack Compose) - Stack Overflow might also be related.

bitspittle commented 5 months ago

Thanks for the extra information! It's been very interesting to read.

It's true that the Box, Column, and Row concepts do deviate in some ways from some specific Android use-cases, but it's definitely a VERY useful concept to have in Kobweb anyway, because in a lot of cases, it does work close enough to what Android devs are expecting, and it's way way way easier to use them than flex layouts.

In contrast, wrapContentWidth is I believe a kind of niche concept (let me know if I'm wrong!) and width(Width.FitContent), our current replacement for it, actually reads pretty well to me.

I think I'd be more inclined to add stuff like wrapContentWidth (and other Jetpack Compose concepts that are currently missing) if I'm convinced people use it a lot in Android code and especially if the equivalent HTML code we would replace it with is complex.

Of course the calculations can change here if we find, as you said, someone else chiming in with a more nuanced implementation for these methods OR if I end up learning that wrapContentWidth is used just everywhere across all the Android codebases :)