mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
remove functionality
This commit is contained in:
@@ -45,6 +45,12 @@ class NodeList:
|
||||
self._nodes_set.add(widget)
|
||||
self._updates += 1
|
||||
|
||||
def _remove(self, widget: Widget) -> None:
|
||||
if widget in self._nodes_set:
|
||||
del self._nodes[self._nodes.index(widget)]
|
||||
self._nodes_set.remove(widget)
|
||||
self._updates += 1
|
||||
|
||||
def _clear(self) -> None:
|
||||
if self._nodes:
|
||||
self._nodes.clear()
|
||||
|
||||
@@ -884,6 +884,16 @@ class App(Generic[ReturnType], DOMNode):
|
||||
for _widget_id, widget in name_widgets:
|
||||
widget.post_message_no_wait(events.Mount(sender=parent))
|
||||
|
||||
def unregister(self, widget: Widget) -> None:
|
||||
"""Unregister a widget.
|
||||
|
||||
Args:
|
||||
widget (Widget): _description_
|
||||
"""
|
||||
if isinstance(widget._parent, Widget):
|
||||
widget._parent.children._remove(widget)
|
||||
self.registry.discard(widget)
|
||||
|
||||
async def _disconnect_devtools(self):
|
||||
await self.devtools.disconnect()
|
||||
|
||||
@@ -906,9 +916,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
child = self.registry.pop()
|
||||
await child.close_messages()
|
||||
|
||||
async def remove(self, child: MessagePump) -> None:
|
||||
self.registry.remove(child)
|
||||
|
||||
async def shutdown(self):
|
||||
await self._disconnect_devtools()
|
||||
driver = self._driver
|
||||
|
||||
@@ -24,6 +24,7 @@ from .parse import parse_selectors
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..dom import DOMNode
|
||||
from ..widget import Widget
|
||||
|
||||
|
||||
class NoMatchingNodesError(Exception):
|
||||
@@ -36,14 +37,16 @@ class DOMQuery:
|
||||
self,
|
||||
node: DOMNode | None = None,
|
||||
selector: str | None = None,
|
||||
nodes: list[DOMNode] | None = None,
|
||||
nodes: list[Widget] | None = None,
|
||||
) -> None:
|
||||
from ..widget import Widget
|
||||
|
||||
self._selector = selector
|
||||
self._nodes: list[DOMNode] = []
|
||||
self._nodes: list[Widget] = []
|
||||
if nodes is not None:
|
||||
self._nodes = nodes
|
||||
elif node is not None:
|
||||
self._nodes = list(node.walk_children())
|
||||
self._nodes = [node for node in node.walk_children()]
|
||||
else:
|
||||
self._nodes = []
|
||||
|
||||
@@ -58,9 +61,12 @@ class DOMQuery:
|
||||
"""True if non-empty, otherwise False."""
|
||||
return bool(self._nodes)
|
||||
|
||||
def __iter__(self) -> Iterator[DOMNode]:
|
||||
def __iter__(self) -> Iterator[Widget]:
|
||||
return iter(self._nodes)
|
||||
|
||||
def __getitem__(self, index: int) -> DOMNode:
|
||||
return self._nodes[index]
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield self._nodes
|
||||
|
||||
@@ -73,6 +79,7 @@ class DOMQuery:
|
||||
Returns:
|
||||
DOMQuery: New DOM Query.
|
||||
"""
|
||||
|
||||
selector_set = parse_selectors(selector)
|
||||
query = DOMQuery(
|
||||
nodes=[_node for _node in self._nodes if match(selector_set, _node)]
|
||||
@@ -94,7 +101,7 @@ class DOMQuery:
|
||||
)
|
||||
return query
|
||||
|
||||
def first(self) -> DOMNode:
|
||||
def first(self) -> Widget:
|
||||
"""Get the first matched node.
|
||||
|
||||
Returns:
|
||||
@@ -107,6 +114,19 @@ class DOMQuery:
|
||||
f"No nodes match the selector {self._selector!r}"
|
||||
)
|
||||
|
||||
def last(self) -> Widget:
|
||||
"""Get the last matched node.
|
||||
|
||||
Returns:
|
||||
DOMNode: A DOM Node.
|
||||
"""
|
||||
if self._nodes:
|
||||
return self._nodes[-1]
|
||||
else:
|
||||
raise NoMatchingNodesError(
|
||||
f"No nodes match the selector {self._selector!r}"
|
||||
)
|
||||
|
||||
def add_class(self, *class_names: str) -> DOMQuery:
|
||||
"""Add the given class name(s) to nodes."""
|
||||
for node in self._nodes:
|
||||
@@ -125,6 +145,12 @@ class DOMQuery:
|
||||
node.toggle_class(*class_names)
|
||||
return self
|
||||
|
||||
def remove(self) -> DOMQuery:
|
||||
"""Remove matched nodes from the DOM"""
|
||||
for node in self._nodes:
|
||||
node.remove()
|
||||
return self
|
||||
|
||||
def set_styles(self, css: str | None = None, **styles: str) -> DOMQuery:
|
||||
"""Set styles on matched nodes.
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ class DOMNode(MessagePump):
|
||||
"""
|
||||
Returns: ``True`` if this DOMNode is displayed (``display != "none"``), ``False`` otherwise.
|
||||
"""
|
||||
return self.styles.display != "none"
|
||||
return self.styles.display != "none" and not (self._closing or self._closed)
|
||||
|
||||
@display.setter
|
||||
def display(self, new_val: bool | str) -> None:
|
||||
|
||||
@@ -130,6 +130,10 @@ class Unmount(Event, bubble=False):
|
||||
"""Sent when a widget is unmounted, and may no longer receive messages."""
|
||||
|
||||
|
||||
class Remove(Event, bubble=False):
|
||||
"""Sent to a widget to ask it to remove itself from the DOM."""
|
||||
|
||||
|
||||
class Show(Event, bubble=False):
|
||||
"""Sent when a widget has become visible."""
|
||||
|
||||
|
||||
@@ -214,16 +214,19 @@ class MessagePump:
|
||||
|
||||
async def close_messages(self) -> None:
|
||||
"""Close message queue, and optionally wait for queue to finish processing."""
|
||||
if self._closed:
|
||||
if self._closed or self._closing:
|
||||
return
|
||||
|
||||
print(self, "close_messages")
|
||||
self._closing = True
|
||||
await self._message_queue.put(MessagePriority(None))
|
||||
for task in self._child_tasks:
|
||||
self.app.unregister(self)
|
||||
cancel_tasks = list(self._child_tasks)
|
||||
for task in cancel_tasks:
|
||||
task.cancel()
|
||||
for task in cancel_tasks:
|
||||
await task
|
||||
self._child_tasks.clear()
|
||||
if self._task is not None:
|
||||
if self._task is not None and asyncio.current_task() != self._task:
|
||||
# Ensure everything is closed before returning
|
||||
await self._task
|
||||
|
||||
|
||||
@@ -181,6 +181,16 @@ class Widget(DOMNode):
|
||||
self.scroll_to(0, 0, animate=False)
|
||||
|
||||
def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None:
|
||||
"""Mount child widgets (making this widget a container).
|
||||
|
||||
Widgets may be passed as positional arguments or keyword arguments. If keyword arguments,
|
||||
the keys will be set as the Widget's id.
|
||||
|
||||
Example:
|
||||
self.mount(Static("hello"), header=Header())
|
||||
|
||||
|
||||
"""
|
||||
self.app.register(self, *anon_widgets, **widgets)
|
||||
self.screen.refresh()
|
||||
|
||||
@@ -815,6 +825,17 @@ class Widget(DOMNode):
|
||||
)
|
||||
return delta
|
||||
|
||||
def scroll_visible(self) -> bool:
|
||||
"""Scroll the container to make this widget visible.
|
||||
|
||||
Returns:
|
||||
bool: True if the parent was scrolled.
|
||||
"""
|
||||
parent = self.parent
|
||||
if isinstance(parent, Widget):
|
||||
return parent.scroll_to_widget(self)
|
||||
return False
|
||||
|
||||
def __init_subclass__(
|
||||
cls,
|
||||
can_focus: bool = False,
|
||||
@@ -1051,6 +1072,10 @@ class Widget(DOMNode):
|
||||
|
||||
self.check_idle()
|
||||
|
||||
def remove(self) -> None:
|
||||
"""Remove the Widget from the DOM (effectively deleting it)"""
|
||||
self.post_message_no_wait(events.Remove(self))
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
"""Get renderable for widget.
|
||||
|
||||
@@ -1122,6 +1147,10 @@ class Widget(DOMNode):
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
await self.dispatch_key(event)
|
||||
|
||||
async def on_remove(self, event: events.Remove) -> None:
|
||||
await self.close_messages()
|
||||
self.parent.refresh(layout=True)
|
||||
|
||||
def on_mount(self, event: events.Mount) -> None:
|
||||
widgets = list(self.compose())
|
||||
if widgets:
|
||||
|
||||
Reference in New Issue
Block a user