Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .codex/skills/flet-validation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ When a property has validation, document it in that property’s docstring (goog
`sdk/python/packages/flet/src/flet/utils/validation.py`.
- Each `V.*` helper includes `Property docstring Raises wording`.
- Keep property `Raises` entries as negations of the annotation rule.
- For strict inequalities, say `"strictly"`.
For example: `V.gt(x)` -> `ValueError: If it is not strictly greater than \`x\`.`
- For sign-neutral divisibility helpers (`factor_of`, `multiple_of`), add
explicit sign rules (`V.gt(0)` or `V.lt(0)`) when direction matters, and
include separate `Raises` entries for those sign rules.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Bug fixes

* Fix `flet build` and `flet publish` dependency parsing for `project.dependencies` and Poetry constraints with `<`/`<=`, and add coverage for normalized requirement handling ([#6332](https://github.com/flet-dev/flet/issues/6332), [#6340](https://github.com/flet-dev/flet/pull/6340)) by @td3447.
* Handle unbounded width in `ResponsiveRow` with an explicit error, treat child controls with `col=0` as hidden, and clarify `Container` expansion behavior when `alignment` is set ([#1951](https://github.com/flet-dev/flet/issues/1951), [#3805](https://github.com/flet-dev/flet/issues/3805), [#5209](https://github.com/flet-dev/flet/issues/5209), [#6354](https://github.com/flet-dev/flet/pull/6354)) by @ndonkoHenri.

### Other changes

Expand Down
8 changes: 4 additions & 4 deletions client/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -911,10 +911,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.18"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
Expand Down Expand Up @@ -1628,10 +1628,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.9"
version: "0.7.10"
torch_light:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions packages/flet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### Bug fixes

* Handle unbounded width in `ResponsiveRow` with an explicit error and treat child controls with `col=0` as hidden at the current breakpoint ([#1951](https://github.com/flet-dev/flet/issues/1951), [#3805](https://github.com/flet-dev/flet/issues/3805), [#6354](https://github.com/flet-dev/flet/pull/6354)) by @ndonkoHenri.

### Other changes

## 0.84.0
Expand Down
31 changes: 29 additions & 2 deletions packages/flet/lib/src/controls/responsive_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ class ResponsiveRowControl extends StatelessWidget with FletStoreMixin {
return withPageSize((context, view) {
var result = LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// breakpoints
if (!constraints.hasBoundedWidth) {
return const ErrorControl(
"Error displaying ResponsiveRow: width is unbounded.",
description:
"Set a fixed width, a non-zero expand, or place it inside a control with bounded width.",
);
}

// Resolve the active breakpoint map once per layout pass. Responsive
// properties such as `columns`, `spacing`, and child `col` values are
// all interpreted against this map.
final rawBreakpoints =
control.get<Map>("breakpoints", view.breakpoints)!;
final breakpoints = <String, double>{};
Expand All @@ -45,13 +55,28 @@ class ResponsiveRowControl extends StatelessWidget with FletStoreMixin {
for (var ctrl in control.children("controls")) {
final col = ctrl.getResponsiveNumber("col", 12)!;
var bpCol = getBreakpointNumber(col, view.size.width, breakpoints);

// `col=0` means "do not occupy any columns" for the current
// breakpoint, so the child should not participate in layout.
if (bpCol <= 0) {
continue;
}

totalCols += bpCol;

// calculate child width
// Convert virtual columns into a fixed pixel width for this child.
// We first remove the total horizontal gaps from the available width,
// then divide the remaining width across the configured columns.
var colWidth =
(constraints.maxWidth - bpSpacing * (bpColumns - 1)) / bpColumns;
var childWidth = colWidth * bpCol + bpSpacing * (bpCol - 1);

// Guard against tiny/invalid available widths so Flutter never sees
// negative box constraints.
if (childWidth < 0) {
childWidth = 0;
}

controls.add(ConstrainedBox(
constraints:
BoxConstraints(minWidth: childWidth, maxWidth: childWidth),
Expand All @@ -62,6 +87,8 @@ class ResponsiveRowControl extends StatelessWidget with FletStoreMixin {
var wrap = (totalCols > bpColumns);

try {
// Keep a single row when everything fits; otherwise switch to Wrap so
// children can continue on the next line.
return wrap
? Wrap(
direction: Axis.horizontal,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import pytest
import pytest_asyncio

import flet as ft
import flet.testing as ftt


@pytest.mark.asyncio(loop_scope="module")
# Create a new flet_app instance for each test method
@pytest_asyncio.fixture(scope="function", autouse=True)
def flet_app(flet_app_function):
return flet_app_function


@pytest.mark.asyncio(loop_scope="function")
async def test_basic(flet_app: ftt.FletTestApp, request):
await flet_app.assert_control_screenshot(
request.node.name,
Expand All @@ -30,3 +37,29 @@ async def test_basic(flet_app: ftt.FletTestApp, request):
],
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_unbounded_height(flet_app: ftt.FletTestApp, request):
flet_app.page.theme_mode = ft.ThemeMode.LIGHT
await flet_app.assert_control_screenshot(
request.node.name,
ft.Column(
controls=[
ft.PageView(
controls=[
ft.Container(
bgcolor=ft.Colors.PURPLE,
alignment=ft.Alignment.CENTER,
content=ft.Text("One", color=ft.Colors.WHITE),
),
ft.Container(
bgcolor=ft.Colors.TEAL,
alignment=ft.Alignment.CENTER,
content=ft.Text("Two", color=ft.Colors.WHITE),
),
],
)
]
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,19 @@ async def test_cupertino_adaptive(flet_app: ftt.FletTestApp, request):
),
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_unbounded_height(flet_app: ftt.FletTestApp, request):
flet_app.page.theme_mode = ft.ThemeMode.LIGHT
await flet_app.assert_control_screenshot(
request.node.name,
ft.Column(
controls=[
ft.Pagelet(
appbar=ft.AppBar(title="Pagelet AppBar"),
content=ft.Text("Pagelet Content"),
)
]
),
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import pytest
import pytest_asyncio

import flet as ft
import flet.testing as ftt


@pytest.mark.asyncio(loop_scope="module")
# Create a new flet_app instance for each test method
@pytest_asyncio.fixture(scope="function", autouse=True)
def flet_app(flet_app_function):
return flet_app_function


@pytest.mark.asyncio(loop_scope="function")
async def test_responsive_row_basic(flet_app: ftt.FletTestApp, request):
await flet_app.assert_control_screenshot(
request.node.name,
Expand All @@ -16,3 +23,51 @@ async def test_responsive_row_basic(flet_app: ftt.FletTestApp, request):
],
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_unbounded_width(flet_app: ftt.FletTestApp, request):
flet_app.page.theme_mode = ft.ThemeMode.LIGHT
await flet_app.assert_control_screenshot(
request.node.name,
ft.Row(
controls=[
ft.ResponsiveRow(
controls=[
ft.Text("Item 1"),
ft.Text("Item 2"),
]
)
]
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_zero_col_controls_are_hidden_at_breakpoint(
flet_app: ftt.FletTestApp, request
):
flet_app.resize_page(360, 240)
flet_app.page.theme_mode = ft.ThemeMode.LIGHT
await flet_app.assert_control_screenshot(
request.node.name,
ft.ResponsiveRow(
controls=[
ft.Container(
col={"xs": 0, "xl": 2},
bgcolor=ft.Colors.GREEN,
content=ft.Text("Left"),
),
ft.Container(
col={"xs": 12, "xl": 8},
bgcolor=ft.Colors.RED,
content=ft.Text("Center"),
),
ft.Container(
col={"xs": 0, "xl": 2},
bgcolor=ft.Colors.BLUE,
content=ft.Text("Right"),
),
],
),
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,32 @@ async def test_no_selected_icon(flet_app: ftt.FletTestApp, request):
),
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_unbounded_height(flet_app: ftt.FletTestApp, request):
flet_app.page.theme_mode = ft.ThemeMode.LIGHT
await flet_app.assert_control_screenshot(
request.node.name,
ft.Column(
controls=[
ft.NavigationRail(
selected_index=0,
label_type=ft.NavigationRailLabelType.ALL,
min_width=100,
destinations=[
ft.NavigationRailDestination(
icon=ft.Icons.FAVORITE_BORDER,
selected_icon=ft.Icons.FAVORITE,
label="First",
),
ft.NavigationRailDestination(
icon=ft.Icons.SETTINGS_OUTLINED,
selected_icon=ft.Icon(ft.Icons.SETTINGS),
label=ft.Text("Settings"),
),
],
)
]
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,35 @@ async def test_disabled_tabs(flet_app: ftt.FletTestApp):
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 1
assert clicked_indexes == [1]


@pytest.mark.asyncio(loop_scope="function")
async def test_unbounded_tabbarview_height(flet_app: ftt.FletTestApp, request):
flet_app.page.theme_mode = ft.ThemeMode.LIGHT
await flet_app.assert_control_screenshot(
name=request.node.name,
control=ft.Column(
controls=[
ft.Tabs(
length=1,
content=ft.Column(
controls=[
ft.TabBar(
tabs=[
ft.Tab(label="Tab 1"),
]
),
ft.TabBarView(
controls=[
ft.Container(
content=ft.Text("Tab 1 content"),
alignment=ft.Alignment.CENTER,
)
],
),
],
),
)
]
),
)
5 changes: 4 additions & 1 deletion sdk/python/packages/flet/src/flet/controls/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ class Control(BaseControl):
Can be a number or a dictionary configured to have a different value for specific
breakpoints, for example `col={"sm": 6}`.
This control spans the 12 virtual columns by default:
A value of `0` hides the control for that breakpoint, so it does not occupy any
columns in the parent :class:`~flet.ResponsiveRow`.
This control spans the 12 virtual columns by default.
| Breakpoint | Dimension |
|---|---|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ResponsiveNumber,
ResponsiveRowBreakpoint,
)
from flet.utils.validation import V, ValidationRules

__all__ = ["ResponsiveNumber", "ResponsiveRow", "ResponsiveRowBreakpoint"]

Expand Down Expand Up @@ -44,17 +45,31 @@ class ResponsiveRow(LayoutControl, AdaptiveControl):
],
)
```

"""

controls: list[Control] = field(default_factory=list)
"""
A list of Controls to display.
"""

__validation_rules__: ValidationRules = (
V.ensure(
lambda ctrl: (
ctrl.columns > 0
if isinstance(ctrl.columns, (int, float))
else all(v > 0 for v in ctrl.columns.values())
),
message="columns must be greater than 0 for all breakpoints",
),
)

columns: ResponsiveNumber = 12
"""
The number of virtual columns to layout children.

Raises:
ValueError: If it is not strictly greater than `0`.
ValueError: If any breakpoint-specific value is not strictly greater than `0`.
"""

alignment: MainAxisAlignment = MainAxisAlignment.START
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ class Container(LayoutControl, AdaptiveControl):

alignment: Optional[Alignment] = None
"""
Defines the alignment of the :attr:`content` inside the container.
Defines the alignment of the :attr:`content` inside this container.
Note:
If `alignment` is non-`None`, this container may expand to fill the
available space from its parent (before positioning its :attr:`content`
within itself according to the given `alignment`) instead of shrinking to its
:attr:`content`. If you need this container to keep a fixed size, give it
container an explicit `width` and/or `height` values, or constrain it via
its parent.
"""

bgcolor: Optional[ColorValue] = None
Expand Down
Loading
Loading