Skip to content

Commit 808f536

Browse files
'Widget.remove_children' should remove direct children only.
Relevant feedback comment: #4183 (comment).
1 parent ef45e72 commit 808f536

File tree

2 files changed

+15
-29
lines changed

2 files changed

+15
-29
lines changed

src/textual/widget.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
from .await_remove import AwaitRemove
5656
from .box_model import BoxModel
5757
from .cache import FIFOCache
58+
from .css.match import match
59+
from .css.parse import parse_selectors
5860
from .css.query import NoMatches, WrongType
5961
from .css.scalar import ScalarOffset
6062
from .dom import DOMNode, NoScreen
@@ -3294,22 +3296,22 @@ def remove(self) -> AwaitRemove:
32943296
await_remove = self.app._remove_nodes([self], self.parent)
32953297
return await_remove
32963298

3297-
def remove_children(
3298-
self, selector: str | type[QueryType] | None = None
3299-
) -> AwaitRemove:
3300-
"""Remove the children of this Widget from the DOM.
3299+
def remove_children(self, selector: str | type[QueryType] = "*") -> AwaitRemove:
3300+
"""Remove the immediate children of this Widget from the DOM.
33013301
33023302
Args:
3303-
selector: A CSS selector to specify which children to remove.
3303+
selector: A CSS selector to specify which direct children to remove.
33043304
33053305
Returns:
3306-
An awaitable object that waits for the children to be removed.
3306+
An awaitable object that waits for the direct children to be removed.
33073307
"""
3308-
if isinstance(selector, str) or selector is None:
3309-
children_to_remove = self.query(selector)
3310-
else:
3311-
children_to_remove = self.query(selector.__name__)
3312-
await_remove = self.app._remove_nodes(list(children_to_remove), self)
3308+
if not isinstance(selector, str):
3309+
selector = selector.__name__
3310+
parsed_selectors = parse_selectors(selector)
3311+
children_to_remove = [
3312+
child for child in self.children if match(parsed_selectors, child)
3313+
]
3314+
await_remove = self.app._remove_nodes(children_to_remove, self)
33133315
return await_remove
33143316

33153317
@asynccontextmanager

tests/test_widget_removing.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,10 @@ async def test_widget_remove_children_with_string_selector():
168168

169169
await app.screen.remove_children("Label")
170170

171-
# All labels are gone but the button and the container remain.
171+
# Only the Screen > Label widget is gone, everything else remains.
172172
assert len(app.query(Button)) == 1
173173
assert len(app.query(Vertical)) == 1
174-
assert len(app.query(Label)) == 0
174+
assert len(app.query(Label)) == 5
175175

176176

177177
async def test_widget_remove_children_with_type_selector():
@@ -198,22 +198,6 @@ async def test_widget_remove_children_with_selector_does_not_leak():
198198
assert len(container.children) == 0
199199

200200

201-
async def test_widget_remove_children_with_compound_selector():
202-
app = ExampleApp()
203-
async with app.run_test():
204-
container = app.query_one(Vertical)
205-
206-
# 6 labels in total, with 5 of them inside the container.
207-
assert len(app.query(Label)) == 6
208-
assert len(container.children) == 5
209-
210-
await app.screen.remove_children("Vertical > Label")
211-
212-
# The labels inside the container are gone, and the 1 outside remains.
213-
assert len(app.query(Label)) == 1
214-
assert len(container.children) == 0
215-
216-
217201
async def test_widget_remove_children_no_children():
218202
app = ExampleApp()
219203
async with app.run_test():

0 commit comments

Comments
 (0)