mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
29
docs/examples/widgets/table.py
Normal file
29
docs/examples/widgets/table.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import csv
|
||||
import io
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import DataTable
|
||||
|
||||
CSV = """lane,swimmer,country,time
|
||||
4,Joseph Schooling,Singapore,50.39
|
||||
2,Michael Phelps,United States,51.14
|
||||
5,Chad le Clos,South Africa,51.14
|
||||
6,László Cseh,Hungary,51.14
|
||||
3,Li Zhuhao,China,51.26
|
||||
8,Mehdy Metella,France,51.58
|
||||
7,Tom Shields,United States,51.73
|
||||
1,Aleksandr Sadovnikov,Russia,51.84"""
|
||||
|
||||
|
||||
class TableApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield DataTable()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
table = self.query_one(DataTable)
|
||||
rows = csv.reader(io.StringIO(CSV))
|
||||
table.add_columns(*next(rows))
|
||||
table.add_rows(rows)
|
||||
|
||||
|
||||
app = TableApp()
|
||||
@@ -25,13 +25,13 @@ You can install Textual via PyPI.
|
||||
If you plan on developing Textual apps, then you should install `textual[dev]`. The `[dev]` part installs a few extra dependencies for development.
|
||||
|
||||
```
|
||||
pip install "textual[dev]==0.2.0b4"
|
||||
pip install "textual[dev]==0.2.0b5"
|
||||
```
|
||||
|
||||
If you only plan on _running_ Textual apps, then you can drop the `[dev]` part:
|
||||
|
||||
```
|
||||
pip install textual==0.2.0b4
|
||||
pip install textual==0.2.0b5
|
||||
```
|
||||
|
||||
!!! important
|
||||
|
||||
1
docs/reference/data_table.md
Normal file
1
docs/reference/data_table.md
Normal file
@@ -0,0 +1 @@
|
||||
::: textual.widgets.DataTable
|
||||
@@ -1 +1,38 @@
|
||||
# DataTable
|
||||
|
||||
A data table widget.
|
||||
|
||||
- [x] Focusable
|
||||
- [ ] Container
|
||||
|
||||
## Example
|
||||
|
||||
The example below populates a table with CSV data.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/widgets/table.py"}
|
||||
```
|
||||
|
||||
=== "table.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/table.py"
|
||||
```
|
||||
|
||||
|
||||
## Reactive Attributes
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --------------- | ------ | ------- | ---------------------------------- |
|
||||
| `show_header` | `bool` | `True` | Show the table header |
|
||||
| `fixed_rows` | `int` | `0` | Number of fixed rows |
|
||||
| `fixed_columns` | `int` | `0` | Number of fixed columns |
|
||||
| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows |
|
||||
| `header_height` | `int` | `1` | Height of header row |
|
||||
| `show_cursor` | `bool` | `True` | Show a cell cursor |
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
* [Table][textual.widgets.DataTable] code reference
|
||||
|
||||
32
poetry.lock
generated
32
poetry.lock
generated
@@ -429,11 +429,11 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.950"
|
||||
version = "0.982"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3"
|
||||
@@ -842,7 +842,7 @@ dev = ["aiohttp", "click", "msgpack"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "6289417d4b26235ab102bacd8444ece650647bfd11e60b2b26709664052a28c0"
|
||||
content-hash = "84203bb5193474eb9204f4f808739cb25e61f02a38d0062ea2ea71d3703573c1"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = []
|
||||
@@ -1086,31 +1086,7 @@ multidict = [
|
||||
{file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"},
|
||||
{file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b"},
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0"},
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22"},
|
||||
{file = "mypy-0.950-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb"},
|
||||
{file = "mypy-0.950-cp310-cp310-win_amd64.whl", hash = "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334"},
|
||||
{file = "mypy-0.950-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f"},
|
||||
{file = "mypy-0.950-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc"},
|
||||
{file = "mypy-0.950-cp36-cp36m-win_amd64.whl", hash = "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2"},
|
||||
{file = "mypy-0.950-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed"},
|
||||
{file = "mypy-0.950-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075"},
|
||||
{file = "mypy-0.950-cp37-cp37m-win_amd64.whl", hash = "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605"},
|
||||
{file = "mypy-0.950-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2"},
|
||||
{file = "mypy-0.950-cp38-cp38-win_amd64.whl", hash = "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2"},
|
||||
{file = "mypy-0.950-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519"},
|
||||
{file = "mypy-0.950-cp39-cp39-win_amd64.whl", hash = "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef"},
|
||||
{file = "mypy-0.950-py3-none-any.whl", hash = "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb"},
|
||||
{file = "mypy-0.950.tar.gz", hash = "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de"},
|
||||
]
|
||||
mypy = []
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "textual"
|
||||
version = "0.2.0b4"
|
||||
version = "0.2.0b5"
|
||||
homepage = "https://github.com/Textualize/textual"
|
||||
description = "Modern Text User Interface framework"
|
||||
authors = ["Will McGugan <will@textualize.io>"]
|
||||
@@ -19,14 +19,7 @@ classifiers = [
|
||||
"Typing :: Typed",
|
||||
]
|
||||
include = [
|
||||
"src/textual/py.typed",
|
||||
"src/textual/cli/py.typed",
|
||||
"src/textual/css/py.typed",
|
||||
"src/textual/devtools/py.typed",
|
||||
"src/textual/drivers/py.typed",
|
||||
"src/textual/layouts/py.typed",
|
||||
"src/textual/renderables/py.typed",
|
||||
"src/textual/widgets/py.typed"
|
||||
"src/textual/py.typed"
|
||||
]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
@@ -38,12 +38,12 @@ class TableApp(App):
|
||||
table = self.table = DataTable(id="data")
|
||||
yield table
|
||||
|
||||
table.add_column("Foo", width=20)
|
||||
table.add_column("Bar", width=60)
|
||||
table.add_column("Baz", width=20)
|
||||
table.add_column("Foo", width=16)
|
||||
table.add_column("Bar", width=16)
|
||||
table.add_column("Baz", width=16)
|
||||
table.add_column("Foo")
|
||||
table.add_column("Bar")
|
||||
table.add_column("Baz")
|
||||
table.add_column("Foo")
|
||||
table.add_column("Bar")
|
||||
table.add_column("Baz")
|
||||
|
||||
for n in range(200):
|
||||
height = 1
|
||||
|
||||
0
src/textual/py.typed
Normal file
0
src/textual/py.typed
Normal file
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
Reactable = Union[Widget, App]
|
||||
|
||||
|
||||
ReactiveType = TypeVar("ReactiveType", covariant=True)
|
||||
ReactiveType = TypeVar("ReactiveType")
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from itertools import chain
|
||||
import sys
|
||||
from typing import ClassVar, Generic, NamedTuple, TypeVar, cast
|
||||
from dataclasses import dataclass, field
|
||||
from itertools import chain, zip_longest
|
||||
from typing import ClassVar, Generic, Iterable, NamedTuple, TypeVar, cast
|
||||
|
||||
from rich.console import RenderableType
|
||||
from rich.padding import Padding
|
||||
@@ -12,18 +12,16 @@ from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
from rich.text import Text, TextType
|
||||
|
||||
from .. import events
|
||||
from .. import events, messages
|
||||
from .._cache import LRUCache
|
||||
from .._profile import timer
|
||||
from .._segment_tools import line_crop
|
||||
from .._types import Lines
|
||||
from ..geometry import clamp, Region, Size, Spacing
|
||||
from ..geometry import Region, Size, Spacing, clamp
|
||||
from ..reactive import Reactive
|
||||
from .._profile import timer
|
||||
from ..render import measure
|
||||
from ..scroll_view import ScrollView
|
||||
|
||||
from .. import messages
|
||||
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal
|
||||
else:
|
||||
@@ -55,10 +53,22 @@ class Column:
|
||||
"""Table column."""
|
||||
|
||||
label: Text
|
||||
width: int
|
||||
width: int = 0
|
||||
visible: bool = False
|
||||
index: int = 0
|
||||
|
||||
content_width: int = 0
|
||||
auto_width: bool = False
|
||||
|
||||
@property
|
||||
def render_width(self) -> int:
|
||||
"""Width in cells, required to render a column."""
|
||||
# +2 is to account for space padding either side of the cell
|
||||
if self.auto_width:
|
||||
return self.content_width + 2
|
||||
else:
|
||||
return self.width + 2
|
||||
|
||||
|
||||
@dataclass
|
||||
class Row:
|
||||
@@ -168,23 +178,21 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
self.rows: dict[int, Row] = {}
|
||||
self.data: dict[int, list[CellType]] = {}
|
||||
self.row_count = 0
|
||||
|
||||
self._y_offsets: list[tuple[int, int]] = []
|
||||
|
||||
self._row_render_cache: LRUCache[
|
||||
tuple[int, int, Style, int, int], tuple[Lines, Lines]
|
||||
]
|
||||
self._row_render_cache = LRUCache(1000)
|
||||
|
||||
self._cell_render_cache: LRUCache[tuple[int, int, Style, bool, bool], Lines]
|
||||
self._cell_render_cache = LRUCache(10000)
|
||||
|
||||
self._line_cache: LRUCache[
|
||||
tuple[int, int, int, int, int, int, Style], list[Segment]
|
||||
]
|
||||
self._line_cache = LRUCache(1000)
|
||||
|
||||
self._line_no = 0
|
||||
self._require_update_dimensions: bool = False
|
||||
self._new_rows: set[int] = set()
|
||||
|
||||
show_header = Reactive(True)
|
||||
fixed_rows = Reactive(0)
|
||||
@@ -251,9 +259,16 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
column = clamp(column, self.fixed_columns, len(self.columns) - 1)
|
||||
return Coord(row, column)
|
||||
|
||||
def _update_dimensions(self) -> None:
|
||||
def _update_dimensions(self, new_rows: Iterable[int]) -> None:
|
||||
"""Called to recalculate the virtual (scrollable) size."""
|
||||
total_width = sum(column.width for column in self.columns)
|
||||
for row_index in new_rows:
|
||||
for column, renderable in zip(
|
||||
self.columns, self._get_row_renderables(row_index)
|
||||
):
|
||||
content_width = measure(self.app.console, renderable, 1)
|
||||
column.content_width = max(column.content_width, content_width)
|
||||
|
||||
total_width = sum(column.render_width for column in self.columns)
|
||||
self.virtual_size = Size(
|
||||
total_width,
|
||||
max(len(self._y_offsets), (self.header_height if self.show_header else 0)),
|
||||
@@ -263,8 +278,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
if row_index not in self.rows:
|
||||
return Region(0, 0, 0, 0)
|
||||
row = self.rows[row_index]
|
||||
x = sum(column.width for column in self.columns[:column_index])
|
||||
width = self.columns[column_index].width
|
||||
x = sum(column.render_width for column in self.columns[:column_index])
|
||||
width = self.columns[column_index].render_width
|
||||
height = row.height
|
||||
y = row.y
|
||||
if self.show_header:
|
||||
@@ -272,25 +287,52 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
cell_region = Region(x, y, width, height)
|
||||
return cell_region
|
||||
|
||||
def add_column(self, label: TextType, *, width: int = 10) -> None:
|
||||
def add_columns(self, *labels: TextType) -> None:
|
||||
"""Add a number of columns.
|
||||
|
||||
Args:
|
||||
*labels: Column headers.
|
||||
|
||||
"""
|
||||
for label in labels:
|
||||
self.add_column(label, width=None)
|
||||
|
||||
def add_column(self, label: TextType, *, width: int | None = None) -> None:
|
||||
"""Add a column to the table.
|
||||
|
||||
Args:
|
||||
label (TextType): A str or Text object containing the label (shown top of column)
|
||||
width (int, optional): Width of the column in cells. Defaults to 10.
|
||||
label (TextType): A str or Text object containing the label (shown top of column).
|
||||
width (int, optional): Width of the column in cells or None to fit content. Defaults to None.
|
||||
"""
|
||||
text_label = Text.from_markup(label) if isinstance(label, str) else label
|
||||
self.columns.append(Column(text_label, width, index=len(self.columns)))
|
||||
self._update_dimensions()
|
||||
self.refresh()
|
||||
|
||||
content_width = measure(self.app.console, text_label, 1)
|
||||
if width is None:
|
||||
column = Column(
|
||||
text_label,
|
||||
content_width,
|
||||
index=len(self.columns),
|
||||
content_width=content_width,
|
||||
auto_width=True,
|
||||
)
|
||||
else:
|
||||
column = Column(
|
||||
text_label, width, content_width=content_width, index=len(self.columns)
|
||||
)
|
||||
|
||||
self.columns.append(column)
|
||||
self._require_update_dimensions = True
|
||||
self.check_idle()
|
||||
|
||||
def add_row(self, *cells: CellType, height: int = 1) -> None:
|
||||
"""Add a row.
|
||||
|
||||
Args:
|
||||
*cells: Positional arguments should contain cell data.
|
||||
height (int, optional): The height of a row (in lines). Defaults to 1.
|
||||
"""
|
||||
row_index = self.row_count
|
||||
|
||||
self.data[row_index] = list(cells)
|
||||
self.rows[row_index] = Row(row_index, height, self._line_no)
|
||||
|
||||
@@ -299,10 +341,34 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
|
||||
self.row_count += 1
|
||||
self._line_no += height
|
||||
self._update_dimensions()
|
||||
self.refresh()
|
||||
|
||||
self._new_rows.add(row_index)
|
||||
self._require_update_dimensions = True
|
||||
self.check_idle()
|
||||
|
||||
def add_rows(self, rows: Iterable[Iterable[CellType]]) -> None:
|
||||
"""Add a number of rows.
|
||||
|
||||
Args:
|
||||
rows (Iterable[Iterable[CellType]]): Iterable of rows. A row is an iterable of cells.
|
||||
"""
|
||||
for row in rows:
|
||||
self.add_row(*row)
|
||||
|
||||
def on_idle(self) -> None:
|
||||
if self._require_update_dimensions:
|
||||
self._require_update_dimensions = False
|
||||
new_rows = self._new_rows.copy()
|
||||
self._new_rows.clear()
|
||||
self._update_dimensions(new_rows)
|
||||
|
||||
def refresh_cell(self, row_index: int, column_index: int) -> None:
|
||||
"""Refresh a cell.
|
||||
|
||||
Args:
|
||||
row_index (int): Row index.
|
||||
column_index (int): Column index.
|
||||
"""
|
||||
if row_index < 0 or column_index < 0:
|
||||
return
|
||||
region = self._get_cell_region(row_index, column_index)
|
||||
@@ -330,7 +396,10 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
if data is None:
|
||||
return [empty for _ in self.columns]
|
||||
else:
|
||||
return [default_cell_formatter(datum) or empty for datum in data]
|
||||
return [
|
||||
Text() if datum is None else default_cell_formatter(datum) or empty
|
||||
for datum, _ in zip_longest(data, range(len(self.columns)))
|
||||
]
|
||||
|
||||
def _render_cell(
|
||||
self,
|
||||
@@ -401,7 +470,9 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
fixed_style = self.get_component_styles("datatable--fixed").rich_style
|
||||
fixed_style += Style.from_meta({"fixed": True})
|
||||
fixed_row = [
|
||||
render_cell(row_index, column.index, fixed_style, column.width)[line_no]
|
||||
render_cell(row_index, column.index, fixed_style, column.render_width)[
|
||||
line_no
|
||||
]
|
||||
for column in self.columns[: self.fixed_columns]
|
||||
]
|
||||
else:
|
||||
@@ -423,7 +494,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
row_index,
|
||||
column.index,
|
||||
row_style,
|
||||
column.width,
|
||||
column.render_width,
|
||||
cursor=cursor_column == column.index,
|
||||
hover=hover_column == column.index,
|
||||
)[line_no]
|
||||
@@ -490,7 +561,9 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
cursor_column=cursor_column,
|
||||
hover_column=hover_column,
|
||||
)
|
||||
fixed_width = sum(column.width for column in self.columns[: self.fixed_columns])
|
||||
fixed_width = sum(
|
||||
column.render_width for column in self.columns[: self.fixed_columns]
|
||||
)
|
||||
|
||||
fixed_line: list[Segment] = list(chain.from_iterable(fixed)) if fixed else []
|
||||
scrollable_line: list[Segment] = list(chain.from_iterable(scrollable))
|
||||
@@ -503,14 +576,6 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
return segments
|
||||
|
||||
def render_line(self, y: int) -> list[Segment]:
|
||||
"""Render a line of content.
|
||||
|
||||
Args:
|
||||
y (int): Y Coordinate of line.
|
||||
|
||||
Returns:
|
||||
list[Segment]: A rendered line.
|
||||
"""
|
||||
width, height = self.size
|
||||
scroll_x, scroll_y = self.scroll_offset
|
||||
fixed_top_row_count = sum(
|
||||
@@ -534,9 +599,6 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def on_key(self, event) -> None:
|
||||
await self.dispatch_key(event)
|
||||
|
||||
def _get_cell_border(self) -> Spacing:
|
||||
top = self.header_height if self.show_header else 0
|
||||
top += sum(
|
||||
@@ -544,7 +606,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
for row_index in range(self.fixed_rows)
|
||||
if row_index in self.rows
|
||||
)
|
||||
left = sum(column.width for column in self.columns[: self.fixed_columns])
|
||||
left = sum(column.render_width for column in self.columns[: self.fixed_columns])
|
||||
return Spacing(top, 0, 0, left)
|
||||
|
||||
def _scroll_cursor_in_to_view(self, animate: bool = False) -> None:
|
||||
|
||||
Reference in New Issue
Block a user