Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TabbedContent message mixup if Tabs inside a TabPane #4233

Closed
davep opened this issue Feb 28, 2024 · 1 comment · Fixed by #4285
Closed

TabbedContent message mixup if Tabs inside a TabPane #4233

davep opened this issue Feb 28, 2024 · 1 comment · Fixed by #4285
Assignees
Labels
bug Something isn't working Task

Comments

@davep
Copy link
Contributor

davep commented Feb 28, 2024

I suspect there are a few issues here (or rather a few operations that might suffer from a similar problem) but I think this example perfectly illustrates the confusion that could arise. Consider the following code:

from textual import on
from textual.app import App, ComposeResult
from textual.widgets import TabbedContent, Tabs, TabPane, Button

class TabbedContentMessageStealingApp(App[None]):

    CSS = """
    TabbedContent, TabPane {
        height: 1fr;
        width: 1fr;
    }
    """

    def compose(self) -> ComposeResult:
        with TabbedContent():
            with TabPane("Test"):
                yield Button("Disable", id="disable")
                yield Tabs("One", "Two", "Three", id="test-tabs")

    @on(Button.Pressed, "#disable")
    def disable_test(self) -> None:
        self.query_one("#test-tabs", Tabs).disable("tab-1")

if __name__ == "__main__":
    TabbedContentMessageStealingApp().run()

There is a Tabs within a TabPane within TabbedContent. There is a Button that, when pressed, will disable the first Tab of the Tabs that the developer has added. When pressed, you would expect the Tab with the title One to grey out and become unusable.

Instead what happens is every tab becomes disabled, as does the Button too:

Screenshot 2024-02-28 at 11 33 14

This will, of course, be due to the message leaking up to the TabbedContent and its first tab getting disabled due to it having a similar ID.

Compare what happens with this code, where I give the TabPane an ID that avoids the clash.

from textual import on
from textual.app import App, ComposeResult
from textual.widgets import TabbedContent, Tabs, TabPane, Button

class TabbedContentMessageStealingApp(App[None]):

    CSS = """
    TabbedContent, TabPane {
        height: 1fr;
        width: 1fr;
    }
    """

    def compose(self) -> ComposeResult:
        with TabbedContent():
            with TabPane("Test", id="avoid-me"):
                yield Button("Disable", id="disable")
                yield Tabs("One", "Two", "Three", id="test-tabs")

    @on(Button.Pressed, "#disable")
    def disable_test(self) -> None:
        self.query_one("#test-tabs", Tabs).disable("tab-1")

if __name__ == "__main__":
    TabbedContentMessageStealingApp().run()

Screenshot 2024-02-28 at 11 35 19

It feels like something should be done to isolate the TabbedContent from any Tabs that might be in one or more of its panes.

@davep davep added bug Something isn't working Task labels Feb 28, 2024
davep added a commit to davep/textual-sandbox that referenced this issue Feb 28, 2024
@davep davep self-assigned this Mar 12, 2024
davep added a commit to davep/textual that referenced this issue Mar 12, 2024
Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Task
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant