Repository: Textualize/textual

Issue:
Shouldn't be able to type in to a disabled input
If a widget is *enabled* and has focus, then subsequently *disabled*, you are still able to type.

When an input is disabled, it should ignore all interactions.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d887f32b..e9c9c2004 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Fixed exceptions in Pilot tests being silently ignored https://github.com/Textualize/textual/pull/2754
 - Fixed issue where internal data of `OptionList` could be invalid for short window after `clear_options` https://github.com/Textualize/textual/pull/2754
 - Fixed `Tooltip` causing a `query_one` on a lone `Static` to fail https://github.com/Textualize/textual/issues/2723
+- Nested widgets wouldn't lose focus when parent is disabled https://github.com/Textualize/textual/issues/2772
 
 ### Changed
 
diff --git a/src/textual/widget.py b/src/textual/widget.py
index 99e4b78eb..3306da7dd 100644
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -2743,7 +2743,17 @@ class Widget(DOMNode):
 
     def watch_disabled(self) -> None:
         """Update the styles of the widget and its children when disabled is toggled."""
-        self.blur()
+        from .app import ScreenStackError
+
+        try:
+            if (
+                self.disabled
+                and self.app.focused is not None
+                and self in self.app.focused.ancestors_with_self
+            ):
+                self.app.focused.blur()
+        except ScreenStackError:
+            pass
         self._update_styles()
 
     def _size_updated(


Test patch:
diff --git a/tests/test_disabled.py b/tests/test_disabled.py
index bc0692ad0..f5edb492c 100644
--- a/tests/test_disabled.py
+++ b/tests/test_disabled.py
@@ -1,15 +1,24 @@
 """Test Widget.disabled."""
 
+import pytest
+
 from textual.app import App, ComposeResult
-from textual.containers import VerticalScroll
+from textual.containers import Vertical, VerticalScroll
 from textual.widgets import (
     Button,
+    Checkbox,
     DataTable,
     DirectoryTree,
     Input,
+    Label,
+    ListItem,
     ListView,
     Markdown,
     MarkdownViewer,
+    OptionList,
+    RadioButton,
+    RadioSet,
+    Select,
     Switch,
     TextLog,
     Tree,
@@ -82,3 +91,59 @@ async def test_disable_via_container() -> None:
             node.has_pseudo_class("disabled") and not node.has_pseudo_class("enabled")
             for node in pilot.app.screen.query("#test-container > *")
         )
+
+
+class ChildrenNoFocusDisabledContainer(App[None]):
+    """App for regression test for https://github.com/Textualize/textual/issues/2772."""
+
+    def compose(self) -> ComposeResult:
+        with Vertical():
+            with Vertical():
+                yield Button()
+                yield Checkbox()
+                yield DataTable()
+                yield DirectoryTree(".")
+                yield Input()
+                with ListView():
+                    yield ListItem(Label("one"))
+                    yield ListItem(Label("two"))
+                    yield ListItem(Label("three"))
+                yield OptionList("one", "two", "three")
+                with RadioSet():
+                    yield RadioButton("one")
+                    yield RadioButton("two")
+                    yield RadioButton("three")
+                yield Select([("one", 1), ("two", 2), ("three", 3)])
+                yield Switch()
+
+    def on_mount(self):
+        dt = self.query_one(DataTable)
+        dt.add_columns("one", "two", "three")
+        dt.add_rows([["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]])
+
+
+@pytest.mark.parametrize(
+    "widget",
+    [
+        Button,
+        Checkbox,
+        DataTable,
+        DirectoryTree,
+        Input,
+        ListView,
+        OptionList,
+        RadioSet,
+        Select,
+        Switch,
+    ],
+)
+async def test_children_loses_focus_if_container_is_disabled(widget):
+    """Regression test for https://github.com/Textualize/textual/issues/2772."""
+    app = ChildrenNoFocusDisabledContainer()
+    async with app.run_test() as pilot:
+        app.query(widget).first().focus()
+        await pilot.pause()
+        assert isinstance(app.focused, widget)
+        app.query(Vertical).first().disabled = True
+        await pilot.pause()
+        assert app.focused is None