diff --git a/src/textual/app.py b/src/textual/app.py index 318dc3223..ed3adc280 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -2153,6 +2153,9 @@ class App(Generic[ReturnType], DOMNode): if widget.parent is not None: widget.parent._nodes._remove(widget) + for node in pruned_remove: + node._detach() + # Return the list of widgets that should end up being sent off in a # prune event. return pruned_remove diff --git a/src/textual/reactive.py b/src/textual/reactive.py index 0553d076c..f03624a0c 100644 --- a/src/textual/reactive.py +++ b/src/textual/reactive.py @@ -239,9 +239,10 @@ class Reactive(Generic[ReactiveType]): ) ) - watch_function = getattr(obj, f"watch_{name}", None) - if callable(watch_function): - invoke_watcher(watch_function, old_value, value) + if obj.is_attached: + watch_function = getattr(obj, f"watch_{name}", None) + if callable(watch_function): + invoke_watcher(watch_function, old_value, value) # Process "global" watchers watchers: list[tuple[Reactable, Callable]] @@ -342,7 +343,6 @@ def _watch( callback: A callable to call when the attribute changes. init: True to call watcher initialization. Defaults to True. """ - if not hasattr(obj, "__watchers"): setattr(obj, "__watchers", {}) watchers: dict[str, list[tuple[Reactable, Callable]]] = getattr(obj, "__watchers") diff --git a/src/textual/walk.py b/src/textual/walk.py index d43d40b06..0dc672471 100644 --- a/src/textual/walk.py +++ b/src/textual/walk.py @@ -53,7 +53,7 @@ def walk_depth_first( """ from textual.dom import DOMNode - stack: list[Iterator[DOMNode]] = [iter(root._nodes)] + stack: list[Iterator[DOMNode]] = [iter(root.children)] pop = stack.pop push = stack.append check_type = filter_type or DOMNode