Skip to content
Merged
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
34 changes: 32 additions & 2 deletions packages/flet/lib/src/controls/tabs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ class TabBarViewControl extends StatelessWidget {
clipBehavior:
control.getClipBehavior("clip_behavior", Clip.hardEdge)!,
viewportFraction: control.getDouble("viewport_fraction", 1.0)!,
physics:
control.disabled ? const NeverScrollableScrollPhysics() : null,
children: control.buildWidgets("controls"),
));
}
Expand Down Expand Up @@ -295,7 +297,10 @@ class _TabBarControlState extends State<TabBarControl> {
.getTextStyle("unselected_label_text_style", Theme.of(context));
var splashBorderRadius =
widget.control.getBorderRadius("splash_border_radius");
final tabs = widget.control.children("tabs").map((tab) {

final tabBarDisabled = widget.control.disabled;
final tabControls = widget.control.children("tabs");
final tabs = tabControls.map((tab) {
// Ensure parent gets rebuilt when a tab becomes visible/invisible.
tab.notifyParent = true;

Expand All @@ -310,10 +315,32 @@ class _TabBarControlState extends State<TabBarControl> {
}).toList();

void onTap(int index) {
if (tabBarDisabled ||
(index >= 0 &&
index < tabControls.length &&
tabControls[index].disabled)) {
final fallbackIndex = tabController.previousIndex
.clamp(0, tabController.length - 1)
.toInt();
if (fallbackIndex != tabController.index) {
// TabBar already requested the tap target via controller.animateTo().
// Restore the previous index so disabled tabs cannot be selected.
tabController.index = fallbackIndex;
}
return;
}

widget.control.triggerEvent("click", index);
}

void onHover(bool hovering, int? index) {
if (tabBarDisabled ||
(index != null &&
index >= 0 &&
index < tabControls.length &&
tabControls[index].disabled)) {
return;
}
widget.control
.triggerEvent("hover", {"hovering": hovering, "index": index});
}
Expand Down Expand Up @@ -383,7 +410,10 @@ class _TabBarControlState extends State<TabBarControl> {
onHover: onHover);
}

return BaseControl(control: widget.control, child: tabBar);
return BaseControl(
control: widget.control,
child: IgnorePointer(ignoring: tabBarDisabled, child: tabBar),
);
} else {
return const ErrorControl("TabBar must be used within a Tabs control");
}
Expand Down
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(
name=request.node.name,
Expand Down Expand Up @@ -38,7 +45,7 @@ async def test_basic(flet_app: ftt.FletTestApp, request):
)


@pytest.mark.asyncio(loop_scope="module")
@pytest.mark.asyncio(loop_scope="function")
async def test_nesting(flet_app: ftt.FletTestApp, request):
await flet_app.assert_control_screenshot(
name=request.node.name,
Expand Down Expand Up @@ -89,3 +96,178 @@ async def test_nesting(flet_app: ftt.FletTestApp, request):
),
),
)


@pytest.mark.asyncio(loop_scope="function")
async def test_disabled_tab(flet_app: ftt.FletTestApp):
clicked_indexes = []
flet_app.page.padding = 0
flet_app.resize_page(300, 300)
flet_app.page.add(
tabs := ft.Tabs(
selected_index=0,
length=3,
expand=True,
content=ft.Column(
expand=True,
controls=[
ft.TabBar(
scrollable=False,
on_click=lambda e: clicked_indexes.append(int(e.data)),
tabs=[
ft.Tab(label="Tab 1"),
tab_2 := ft.Tab(label="Tab 2"),
ft.Tab(label="Tab 3"),
],
),
ft.TabBarView(
expand=True,
controls=[
ft.Text("View 1"),
ft.Text("View 2"),
ft.Text("View 3"),
],
),
],
),
)
)
await flet_app.tester.pump_and_settle()

# click tab2
await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 1
assert clicked_indexes == [1]

# disable tab2
tabs.selected_index = 0
tab_2.disabled = True
flet_app.page.update()
await flet_app.tester.pump_and_settle()

# click tab2 (disabled)
await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 0
assert clicked_indexes == [1]

# re-enable tab2
tab_2.disabled = False
flet_app.page.update()
await flet_app.tester.pump_and_settle()

# click tab2
await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 1
assert clicked_indexes == [1, 1]


@pytest.mark.asyncio(loop_scope="function")
async def test_disabled_tabbar(flet_app: ftt.FletTestApp):
clicked_indexes = []
flet_app.page.padding = 0
flet_app.resize_page(300, 300)
flet_app.page.add(
tabs := ft.Tabs(
selected_index=0,
length=3,
expand=True,
content=ft.Column(
expand=True,
controls=[
tab_bar := ft.TabBar(
disabled=True,
scrollable=False,
on_click=lambda e: clicked_indexes.append(int(e.data)),
tabs=[
ft.Tab(label="Tab 1"),
ft.Tab(label="Tab 2"),
ft.Tab(label="Tab 3"),
],
),
ft.TabBarView(
expand=True,
controls=[
ft.Text("View 1"),
ft.Text("View 2"),
ft.Text("View 3"),
],
),
],
),
)
)
await flet_app.tester.pump_and_settle()

# click tab2 (disabled TabBar)
await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 0
assert clicked_indexes == []

# re-enable tabbar
tab_bar.disabled = False
flet_app.page.update()
await flet_app.tester.pump_and_settle()

await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
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_disabled_tabs(flet_app: ftt.FletTestApp):
clicked_indexes = []
flet_app.page.padding = 0
flet_app.resize_page(300, 300)
flet_app.page.add(
tabs := ft.Tabs(
disabled=True,
selected_index=0,
length=3,
expand=True,
content=ft.Column(
expand=True,
controls=[
ft.TabBar(
scrollable=False,
on_click=lambda e: clicked_indexes.append(int(e.data)),
tabs=[
ft.Tab(label="Tab 1"),
ft.Tab(label="Tab 2"),
ft.Tab(label="Tab 3"),
],
),
ft.TabBarView(
expand=True,
controls=[
ft.Text("View 1"),
ft.Text("View 2"),
ft.Text("View 3"),
],
),
],
),
)
)
await flet_app.tester.pump_and_settle()

# click tab2 (disabled Tabs)
await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 0
assert clicked_indexes == []

# re-enable Tabs
tabs.disabled = False
flet_app.page.update()
await flet_app.tester.pump_and_settle()

# click tab2
await flet_app.tester.tap((await flet_app.tester.find_by_text("Tab 2")).first)
await flet_app.tester.pump_and_settle()
assert tabs.selected_index == 1
assert clicked_indexes == [1]
Loading