Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Textual-Window Changelog

## [0.8.2] 2026-07-19

### Changed

- Relaxed the Textual version constraint to `>=5.1.0,<9.0.0` to allow the latest Textual (verified against 8.2.8).
- Added a `TypedAppMixin` typing shim in `button_bases.py`. Textual 8.x declares `DOMNode.app` with `getters.app(App)`, which resolves to `App[Unknown]` and fails strict type checking; the mixin redeclares it as `App[object]` with no runtime effect.
- [dev] Added Textual 8.2 to the Nox test matrix alongside 5.1.
- [dev] Regenerated the snapshot baseline under Textual 8.2.8 and Python 3.9 (the version CI and `.python-version` use — Rich's SVG output is sensitive to both the Rich and Python versions). The snapshot test is skipped on Textual < 8, since older Rich versions emit a different SVG format.
- [dev] Updated `uv.lock` so the default dev environment uses the latest Textual.

## [0.8.1] 2025-08-01

- Updated `ezpubsub` dependency to version 0.3.0
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

# PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12"]
PYTHON_VERSIONS = ["3.9"]
MAJOR_TEXTUAL_VERSIONS = [5.1]
MAJOR_TEXTUAL_VERSIONS = [5.1, 8.2]

##############
# NOX CONFIG #
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "textual-window"
version = "0.8.1"
version = "0.8.2"
description = "A Textual widget for a floating, draggable window and included window bar/manager system."
readme = "README.md"
requires-python = ">=3.9"
Expand All @@ -24,7 +24,7 @@ classifiers = [

dependencies = [
"ezpubsub>=0.3.0",
"textual>=5.1.0,<6.0.0",
"textual>=5.1.0,<9.0.0",
]

[project.urls]
Expand Down
23 changes: 22 additions & 1 deletion src/textual_window/button_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,34 @@

from __future__ import annotations

from typing import TYPE_CHECKING

from textual.visual import VisualType
from textual import events
from textual.widgets import Static
from textual.message import Message

if TYPE_CHECKING:
from textual.app import App


class TypedAppMixin:
"""Redeclares `self.app` as `App[object]` for the type checkers.

Textual 8.x types `DOMNode.app` with `getters.app(App)`, which resolves to
`App[Unknown]` and fails strict Pyright/MyPy wherever `self.app` is used.
This mixin has no runtime effect."""

if TYPE_CHECKING:

@property
def app(self) -> App[object]: ...

@app.setter
def app(self, app: App[object]) -> None: ...


class NoSelectStatic(Static):
class NoSelectStatic(TypedAppMixin, Static):
"""This class is used in window.py and windowbar.py to create buttons."""

@property
Expand Down
6 changes: 3 additions & 3 deletions src/textual_window/switcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@

# Local imports
from textual_window.manager import window_manager
from textual_window.button_bases import ButtonStatic
from textual_window.button_bases import ButtonStatic, TypedAppMixin

__all__ = [
"WindowSwitcher",
]


class WindowSwitcher(Widget):
class WindowSwitcher(TypedAppMixin, Widget):

def __init__(self, cycle_key: str = "f1", **kwargs: Any) -> None:
super().__init__(**kwargs)
Expand All @@ -49,7 +49,7 @@ def __init__(self, name: str, content: VisualType = "") -> None:
super().__init__(name=name, content=content)


class WindowSwitcherScreen(ModalScreen[None]):
class WindowSwitcherScreen(TypedAppMixin, ModalScreen[None]):

CSS = """
WindowSwitcherScreen { align: center middle; }
Expand Down
3 changes: 2 additions & 1 deletion src/textual_window/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
# Local imports
from textual_window.manager import window_manager
from textual_window.windowcomponents import Resizer, TopBar, BottomBar
from textual_window.button_bases import TypedAppMixin

__all__ = [
"Window",
Expand Down Expand Up @@ -79,7 +80,7 @@ def control(self) -> Window:
return self.window


class Window(Widget):
class Window(TypedAppMixin, Widget):

DEFAULT_CSS = """
Window {
Expand Down
4 changes: 2 additions & 2 deletions src/textual_window/windowbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

# Local imports
from textual_window.manager import window_manager # The manager is a singleton instance
from textual_window.button_bases import NoSelectStatic, ButtonStatic
from textual_window.button_bases import NoSelectStatic, ButtonStatic, TypedAppMixin

__all__ = [
"WindowBar",
Expand Down Expand Up @@ -245,7 +245,7 @@ async def button_pressed(self, event: ButtonStatic.Pressed) -> None:
self.window_bar.toggle_dock_location()


class WindowBar(Horizontal):
class WindowBar(TypedAppMixin, Horizontal):

DEFAULT_CSS = """
WindowBar {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 12 additions & 3 deletions tests/test_suite.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
from pathlib import Path
import pytest
from textual import __version__ as textual_version
from textual.pilot import Pilot
from textual_window.demo import WindowDemo

# from .app_test import WindowTestApp

DEMO_DIR = Path(__file__).parent
TERINAL_SIZE = (110, 36)

async def test_launch():

async def test_launch():
"""Test launching the WindowDemo app."""
app = WindowDemo()
async with app.run_test() as pilot:
await pilot.pause()
await pilot.exit(None)
await pilot.exit(None)


@pytest.mark.skipif(
int(textual_version.split(".")[0]) < 8,
reason="Snapshot baseline is generated under Textual 8.x; older Rich emits a different SVG format",
)
def test_snapshot_launch_only(snap_compare):

async def run_before(pilot: Pilot[None]) -> None:
Expand All @@ -22,4 +31,4 @@ async def run_before(pilot: Pilot[None]) -> None:
DEMO_DIR / "app_test.py",
terminal_size=TERINAL_SIZE,
run_before=run_before,
)
)
24 changes: 11 additions & 13 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading