Enhanced Textual TUI with proper API usage and documentation
- Fixed DataTable row selection and event handling - Added explicit column keys to prevent auto-generated keys - Implemented row-to-flow mapping for reliable selection tracking - Converted left metrics panel to horizontal top bar - Fixed all missing FlowStats/EnhancedAnalysisData attributes - Created comprehensive Textual API documentation in Documentation/textual/ - Added validation checklist to prevent future API mismatches - Preserved cursor position during data refreshes - Fixed RowKey type handling and event names The TUI now properly handles flow selection, displays metrics in a compact top bar, and correctly correlates selected rows with the details pane.
This commit is contained in:
242
Documentation/textual/datatable.md
Normal file
242
Documentation/textual/datatable.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# DataTable Widget API Reference
|
||||
|
||||
## Overview
|
||||
|
||||
The DataTable widget displays tabular data with support for selection, scrolling, and styling.
|
||||
|
||||
## Import
|
||||
|
||||
```python
|
||||
from textual.widgets import DataTable
|
||||
```
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
Widget → ScrollView → DataTable
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
```python
|
||||
DataTable(
|
||||
*,
|
||||
show_header: bool = True,
|
||||
fixed_rows: int = 0,
|
||||
fixed_columns: int = 0,
|
||||
zebra_stripes: bool = False,
|
||||
header_height: int = 1,
|
||||
show_cursor: bool = True,
|
||||
cursor_foreground_priority: Literal["renderable", "css"] = "renderable",
|
||||
cursor_background_priority: Literal["renderable", "css"] = "renderable",
|
||||
cursor_type: CursorType = "cell",
|
||||
cell_padding: int = 1,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
disabled: bool = False
|
||||
)
|
||||
```
|
||||
|
||||
## Key Properties
|
||||
|
||||
### Cursor Properties
|
||||
- `cursor_row: int` - Current cursor row (0-based)
|
||||
- `cursor_column: int` - Current cursor column (0-based)
|
||||
- `cursor_coordinate: Coordinate` - (row, column) tuple
|
||||
|
||||
### Table Properties
|
||||
- `rows: Mapping[RowKey, Row]` - Dictionary of rows
|
||||
- `columns: list[Column]` - List of columns
|
||||
- `row_count: int` - Number of rows
|
||||
- `column_count: int` - Number of columns
|
||||
|
||||
## Methods
|
||||
|
||||
### Adding Data
|
||||
|
||||
```python
|
||||
# Add columns
|
||||
add_column(label: TextType, *, width: int | None = None, key: str | None = None, default: CellType | None = None) -> ColumnKey
|
||||
|
||||
# Add multiple columns
|
||||
add_columns(*labels: TextType) -> list[ColumnKey]
|
||||
|
||||
# Add a single row
|
||||
add_row(*cells: CellType, height: int | None = 1, key: str | None = None, label: TextType | None = None) -> RowKey
|
||||
|
||||
# Add multiple rows
|
||||
add_rows(rows: Iterable[Iterable[CellType]]) -> list[RowKey]
|
||||
```
|
||||
|
||||
### Modifying Data
|
||||
|
||||
```python
|
||||
# Update a cell
|
||||
update_cell(row_key: RowKey, column_key: ColumnKey, value: CellType, *, update_width: bool = False) -> None
|
||||
|
||||
# Update cell at coordinate
|
||||
update_cell_at(coordinate: Coordinate, value: CellType, *, update_width: bool = False) -> None
|
||||
|
||||
# Remove row
|
||||
remove_row(row_key: RowKey) -> None
|
||||
|
||||
# Remove column
|
||||
remove_column(column_key: ColumnKey) -> None
|
||||
|
||||
# Clear all data
|
||||
clear(columns: bool = False) -> Self
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
```python
|
||||
# Move cursor
|
||||
move_cursor(row: int | None = None, column: int | None = None, animate: bool = True, scroll: bool = True) -> None
|
||||
|
||||
# Scroll to coordinate
|
||||
scroll_to(x: float | None = None, y: float | None = None, *, animate: bool = True, speed: float | None = None, duration: float | None = None, ...) -> None
|
||||
```
|
||||
|
||||
### Sorting
|
||||
|
||||
```python
|
||||
# Sort by column(s)
|
||||
sort(*columns: ColumnKey | str, reverse: bool = False) -> Self
|
||||
|
||||
# Sort with custom key
|
||||
sort(key: Callable[[Any], Any], *, reverse: bool = False) -> Self
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
### Selection Events
|
||||
|
||||
```python
|
||||
class RowSelected(Message):
|
||||
"""Posted when a row is selected (Enter key)."""
|
||||
row_key: RowKey
|
||||
row_index: int
|
||||
|
||||
class CellSelected(Message):
|
||||
"""Posted when a cell is selected."""
|
||||
coordinate: Coordinate
|
||||
cell_key: CellKey
|
||||
|
||||
class RowHighlighted(Message):
|
||||
"""Posted when cursor highlights a row."""
|
||||
row_key: RowKey
|
||||
row_index: int
|
||||
|
||||
class CellHighlighted(Message):
|
||||
"""Posted when cursor highlights a cell."""
|
||||
coordinate: Coordinate
|
||||
cell_key: CellKey
|
||||
|
||||
class ColumnHighlighted(Message):
|
||||
"""Posted when cursor highlights a column."""
|
||||
column_key: ColumnKey
|
||||
column_index: int
|
||||
```
|
||||
|
||||
### Header Events
|
||||
|
||||
```python
|
||||
class HeaderSelected(Message):
|
||||
"""Posted when a column header is clicked."""
|
||||
column_key: ColumnKey
|
||||
column_index: int
|
||||
label: Text
|
||||
|
||||
class RowLabelSelected(Message):
|
||||
"""Posted when a row label is clicked."""
|
||||
row_key: RowKey
|
||||
row_index: int
|
||||
label: Text
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Creating a DataTable
|
||||
|
||||
```python
|
||||
class MyWidget(Widget):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield DataTable(id="my-table")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
table = self.query_one("#my-table", DataTable)
|
||||
table.add_columns("Name", "Value", "Status")
|
||||
table.add_row("Item 1", "100", "Active", key="item_1")
|
||||
```
|
||||
|
||||
### Handling Selection
|
||||
|
||||
```python
|
||||
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
||||
# React to cursor movement
|
||||
self.selected_row = event.row_key
|
||||
|
||||
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
||||
# React to Enter key press
|
||||
self.process_selection(event.row_key)
|
||||
```
|
||||
|
||||
### Preserving Cursor Position
|
||||
|
||||
```python
|
||||
def refresh_table(self):
|
||||
table = self.query_one(DataTable)
|
||||
|
||||
# Save position
|
||||
cursor_row = table.cursor_row
|
||||
selected_key = list(table.rows.keys())[cursor_row] if table.rows else None
|
||||
|
||||
# Update data
|
||||
table.clear()
|
||||
# ... add new data ...
|
||||
|
||||
# Restore position
|
||||
if selected_key and selected_key in table.rows:
|
||||
row_index = list(table.rows.keys()).index(selected_key)
|
||||
table.move_cursor(row=row_index, animate=False)
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
DataTable supports CSS styling but does NOT have:
|
||||
- `set_row_style()` method
|
||||
- `set_cell_style()` method
|
||||
|
||||
Use CSS classes and selectors instead:
|
||||
|
||||
```css
|
||||
DataTable > .datatable--cursor {
|
||||
background: $primary 30%;
|
||||
}
|
||||
|
||||
DataTable > .datatable--header {
|
||||
text-style: bold;
|
||||
}
|
||||
```
|
||||
|
||||
## Type Hints
|
||||
|
||||
```python
|
||||
from textual.widgets.data_table import RowKey, ColumnKey, CellKey
|
||||
from textual.coordinate import Coordinate
|
||||
|
||||
# RowKey and ColumnKey are not strings - convert with str() if needed
|
||||
row_key_str = str(row_key)
|
||||
```
|
||||
|
||||
## Common Errors and Solutions
|
||||
|
||||
1. **TypeError: 'RowKey' is not iterable**
|
||||
- Solution: Convert to string first: `str(row_key)`
|
||||
|
||||
2. **AttributeError: 'DataTable' has no attribute 'set_row_style'**
|
||||
- Solution: Use CSS styling instead
|
||||
|
||||
3. **Event not firing**
|
||||
- Check handler name: `on_data_table_row_highlighted` not `on_data_table_cursor_moved`
|
||||
Reference in New Issue
Block a user