Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### [7.5.0] - 2026-01-29

- The DataTable row cursor will extend to the full width if there is excess space https://github.com/Textualize/textual/pull/6345
- The DataTable will send a selected event on click, only if the cell / row / column is currently highlighted https://github.com/Textualize/textual/pull/6345

## [7.4.0] - 2026-01-25

### Added
Expand Down Expand Up @@ -3328,6 +3335,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling

[7.5.0]: https://github.com/Textualize/textual/compare/v7.4.0...v7.5.0
[7.4.0]: https://github.com/Textualize/textual/compare/v7.3.0...v7.4.0
[7.3.0]: https://github.com/Textualize/textual/compare/v7.2.0...v7.3.0
[7.2.0]: https://github.com/Textualize/textual/compare/v7.1.0...v7.2.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "7.4.0"
version = "7.5.0"
homepage = "https://github.com/Textualize/textual"
repository = "https://github.com/Textualize/textual"
documentation = "https://textual.textualize.io/"
Expand Down
59 changes: 44 additions & 15 deletions src/textual/widgets/_data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ class RowRenderables(NamedTuple):
class DataTable(ScrollView, Generic[CellType], can_focus=True):
"""A tabular widget that contains data."""

ALLOW_SELECT = False

BINDINGS: ClassVar[list[BindingType]] = [
Binding("enter", "select_cursor", "Select", show=False),
Binding("up", "cursor_up", "Cursor up", show=False),
Expand Down Expand Up @@ -328,7 +330,6 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
color: $foreground;
height: auto;
max-height: 100%;

&.datatable--fixed-cursor {
background: $block-cursor-blurred-background;
}
Expand Down Expand Up @@ -450,7 +451,7 @@ def __init__(
) -> None:
self.data_table = data_table
"""The data table."""
self.value: CellType = value
self.value = value
"""The value in the highlighted cell."""
self.coordinate: Coordinate = coordinate
"""The coordinate of the highlighted cell."""
Expand Down Expand Up @@ -1555,7 +1556,7 @@ def _get_row_region(self, row_index: int) -> Region:
y = sum(ordered_row.height for ordered_row in self.ordered_rows[:row_index])
if self.show_header:
y += self.header_height
row_region = Region(0, y, row_width, row.height)
row_region = Region(0, y, max(self.size.width, row_width), row.height)
return row_region

def _get_column_region(self, column_index: int) -> Region:
Expand Down Expand Up @@ -2357,17 +2358,36 @@ def _render_line_in_row(
)
remaining_space = max(0, widget_width - table_width)
background_color = self.background_colors[1]
Comment on lines 2358 to 2360
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remaining_space is computed from widget_width - table_width, but table_width currently doesn’t include the fixed column widths (only columns from fixed_columns: plus the row-label width). When fixed_columns > 0 this overestimates padding and relies on later trimming, which adds unnecessary work per rendered line. Consider basing the calculation on the full table width (row labels + all columns) or otherwise accounting for fixed columns.

Copilot uses AI. Check for mistakes.
if row_style.bgcolor is not None:
# TODO: This should really be in a component class
faded_color = Color.from_rich_color(row_style.bgcolor).blend(
background_color, factor=0.25
)
faded_style = Style.from_color(
color=row_style.color, bgcolor=faded_color.rich_color
if self.cursor_type == "row":
extend_style, _ = self._get_styles_to_render_cell(
row_index == -1,
False,
False,
should_highlight(
hover_location, Coordinate(row_index or 0, 0), cursor_type
),
row_index == cursor_location.row,
self.show_cursor,
self._show_hover_cursor,
False,
False,
)
extend_style = row_style + extend_style
else:
faded_style = Style.from_color(row_style.color, row_style.bgcolor)
scrollable_row.append([Segment(" " * remaining_space, faded_style)])
if row_style.bgcolor is not None:
# TODO: This should really be in a component class
faded_color = Color.from_rich_color(row_style.bgcolor).blend(
background_color, factor=0.25
)
extend_style = Style.from_color(
color=row_style.color, bgcolor=faded_color.rich_color
)
else:
extend_style = Style.from_color(row_style.color, row_style.bgcolor)
extend_style += Style.from_meta(
{"row": row_index, "column": 0, "out_of_bounds": True}
)
Comment on lines +2387 to +2389
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The padding segment for the “extended” area is tagged with meta {"row": row_index, "column": 0, "out_of_bounds": True}. Because _on_click only checks for presence of row/column, clicking in the extended whitespace will be interpreted as a click on column 0 (and on the header row will incorrectly fire HeaderSelected for column 0). Consider not emitting row/column meta for out-of-bounds padding, or set a sentinel and explicitly handle it in the click logic (e.g., treat it as a row-click when cursor_type == "row", otherwise ignore).

Suggested change
extend_style += Style.from_meta(
{"row": row_index, "column": 0, "out_of_bounds": True}
)
extend_style += Style.from_meta({"out_of_bounds": True})

Copilot uses AI. Check for mistakes.
scrollable_row.append([Segment(" " * remaining_space, extend_style)])

row_pair = (fixed_row, scrollable_row)
self._row_render_cache[cache_key] = row_pair
Expand All @@ -2389,7 +2409,7 @@ def _get_offsets(self, y: int) -> tuple[RowKey, int]:
return self._header_row_key, y
y -= header_height
if y > len(y_offsets):
raise LookupError("Y coord {y!r} is greater than total height")
raise LookupError(f"Y coord {y!r} is greater than total height")

return y_offsets[y]

Expand Down Expand Up @@ -2541,6 +2561,10 @@ def _on_mouse_move(self, event: events.MouseMove):
self._set_hover_cursor(False)
return

if self.cursor_type != "row" and meta.get("out_of_bounds", False):
self._set_hover_cursor(False)
return

if self.show_cursor and self.cursor_type != "none":
try:
self.hover_coordinate = Coordinate(meta["row"], meta["column"])
Expand Down Expand Up @@ -2647,6 +2671,8 @@ async def _on_click(self, event: events.Click) -> None:
meta = event.style.meta
if "row" not in meta or "column" not in meta:
return
if self.cursor_type != "row" and meta.get("out_of_bounds", False):
return

row_index = meta["row"]
column_index = meta["column"]
Expand All @@ -2667,8 +2693,11 @@ async def _on_click(self, event: events.Click) -> None:
self.post_message(message)
elif self.show_cursor and self.cursor_type != "none":
# Only post selection events if there is a visible row/col/cell cursor.
self.cursor_coordinate = Coordinate(row_index, column_index)
self._post_selected_message()
new_coordinate = Coordinate(row_index, column_index)
highlight_click = new_coordinate == self.cursor_coordinate
self.cursor_coordinate = new_coordinate
if highlight_click:
self._post_selected_message()
Comment on lines 2667 to +2700
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new click-selection logic doesn’t account for the out_of_bounds padding segments added during rendering. Since those segments include row/column meta, clicks in the extended whitespace can be misinterpreted as clicks on column 0 / header column 0. Add an out_of_bounds check here and decide whether to ignore the click or map it appropriately (e.g., treat it as a row click when cursor_type == "row").

Copilot uses AI. Check for mistakes.
self._scroll_cursor_into_view(animate=True)
event.stop()

Expand Down
Loading
Loading