pythonnative 0.23.0__py3-none-any.whl → 0.24.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pythonnative/preview.py CHANGED
@@ -231,17 +231,13 @@ class DesktopApp:
231
231
  host.on_pause()
232
232
  except Exception:
233
233
  pass
234
+ # ``on_destroy`` unmounts the host's reconciler: effect cleanups
235
+ # run, native widgets are destroyed, and event registrations are
236
+ # released (see ``screen._destroy_host``).
234
237
  try:
235
238
  host.on_destroy()
236
239
  except Exception:
237
240
  pass
238
- reconciler = getattr(host, "_reconciler", None)
239
- tree = getattr(reconciler, "_tree", None) if reconciler is not None else None
240
- if reconciler is not None and tree is not None:
241
- try:
242
- reconciler._destroy_tree(tree)
243
- except Exception:
244
- pass
245
241
  container = getattr(host, "_pn_container", None)
246
242
  if container is not None:
247
243
  try:
@@ -249,6 +245,29 @@ class DesktopApp:
249
245
  except Exception:
250
246
  pass
251
247
 
248
+ def teardown_all(self) -> None:
249
+ """Destroy every screen on the stack (preview window closing)."""
250
+ while self._stack:
251
+ self._teardown(self._stack.pop())
252
+
253
+ def back_pressed(self) -> None:
254
+ """Route a desktop back gesture (Escape) like a hardware back press.
255
+
256
+ ``use_back_handler`` subscribers on the active screen get the
257
+ first chance to consume the event; otherwise the stack pops
258
+ (matching Android's default back behavior). At the root the
259
+ event is ignored.
260
+ """
261
+ host = self.active_host()
262
+ if host is None:
263
+ return
264
+ try:
265
+ if host.on_back_pressed():
266
+ return
267
+ except Exception:
268
+ pass
269
+ self.pop_screen()
270
+
252
271
  # -- viewport / resize --------------------------------------------
253
272
 
254
273
  def resize(self, width: float, height: float) -> None:
@@ -391,6 +410,12 @@ def run_preview(
391
410
  root_dir, watched = _resolve_paths(component_path, project_root, watch_dir)
392
411
  _publish_desktop_color_scheme()
393
412
 
413
+ # The preview is inherently a development surface: turn on dev
414
+ # diagnostics (validation warnings, hook-order checks, RedBox).
415
+ from . import diagnostics
416
+
417
+ diagnostics.set_dev_mode(True)
418
+
394
419
  root = tk.Tk()
395
420
  root.title(title)
396
421
  root.geometry(f"{int(width)}x{int(height)}")
@@ -414,6 +439,11 @@ def run_preview(
414
439
 
415
440
  stage.bind("<Configure>", _on_configure)
416
441
 
442
+ # Escape acts as the desktop stand-in for the hardware back button:
443
+ # ``use_back_handler`` subscribers can intercept it, and otherwise
444
+ # the navigation stack pops.
445
+ root.bind("<Escape>", lambda _event: app.back_pressed())
446
+
417
447
  watcher = _build_watcher(watched, root_dir, app, main_queue) if hot_reload else None
418
448
  if watcher is not None:
419
449
  watcher.start()
@@ -446,6 +476,12 @@ def run_preview(
446
476
  watcher.stop()
447
477
  except Exception:
448
478
  pass
479
+ # Unmount every screen first so effect cleanups (timers, tasks,
480
+ # subscriptions) run before the Tk interpreter goes away.
481
+ try:
482
+ app.teardown_all()
483
+ except Exception:
484
+ pass
449
485
  runtime_module.set_desktop_main_dispatch(None)
450
486
  desktop_backend.clear_root_container()
451
487
  try: