Skip to content

Commit 9a72e06

Browse files
authored
Merge branch 'main' into feat-events-add-control-property-to-enter-and-leave
2 parents 51409e1 + b9be24f commit 9a72e06

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2329
-1172
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## Unreleased
99

10+
### Changed
11+
12+
- Grid will now size children to the maximum height of a row https://github.com/Textualize/textual/pull/5113
13+
- Markdown links will be opened with `App.open_url` automatically https://github.com/Textualize/textual/pull/5113
14+
- The universal selector (`*`) will now not match widgets with the class `-textual-system` (scrollbars, notifications etc) https://github.com/Textualize/textual/pull/5113
15+
- Renamed `Screen.can_view` and `Widget.can_view` to `Screen.can_view_entire` and `Widget.can_view_entire` https://github.com/Textualize/textual/pull/5174
16+
1017
### Added
1118

19+
- Added Link widget https://github.com/Textualize/textual/pull/5113
20+
- Added `open_links` to `Markdown` and `MarkdownViewer` widgets https://github.com/Textualize/textual/pull/5113
21+
- Added `App.DEFAULT_MODE` https://github.com/Textualize/textual/pull/5113
22+
- Added `Containers.HorizontalGroup` and `Containers.VerticalGroup` https://github.com/Textualize/textual/pull/5113
23+
- Added `$`, `£`, ``, `(`, `)` symbols to Digits https://github.com/Textualize/textual/pull/5113
24+
- Added `Button.action` parameter to invoke action when clicked https://github.com/Textualize/textual/pull/5113
25+
- Added `immediate` parameter to scroll methods https://github.com/Textualize/textual/pull/5164
26+
- Added `textual._loop.loop_from_index` https://github.com/Textualize/textual/pull/5164
27+
- Added `min_color` and `max_color` to Sparklines constructor, which take precedence over CSS https://github.com/Textualize/textual/pull/5174
28+
- Added new demo `python -m textual`, not *quite* finished but better than the old one https://github.com/Textualize/textual/pull/5174
29+
- Added `Screen.can_view_partial` and `Widget.can_view_partial` https://github.com/Textualize/textual/pull/5174
30+
- Added `App.is_web` property to indicate if the app is running via a web browser https://github.com/Textualize/textual/pull/5128
1231
- `Enter` and `Leave` events can now be used with the `on` decorator https://github.com/Textualize/textual/pull/5159
1332

33+
### Fixed
34+
35+
- Fixed glitchy ListView https://github.com/Textualize/textual/issues/5163
36+
1437
## [0.84.0] - 2024-10-22
1538

1639
### Fixed

docs/api/layout.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: "textual.layout"
3+
---
4+
5+
6+
::: textual.layout

docs/examples/widgets/link.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from textual.app import App, ComposeResult
2+
from textual.widgets import Link
3+
4+
5+
class LabelApp(App):
6+
AUTO_FOCUS = None
7+
CSS = """
8+
Screen {
9+
align: center middle;
10+
}
11+
"""
12+
13+
def compose(self) -> ComposeResult:
14+
yield Link(
15+
"Go to textualize.io",
16+
url="https://textualize.io",
17+
tooltip="Click me",
18+
)
19+
20+
21+
if __name__ == "__main__":
22+
app = LabelApp()
23+
app.run()

docs/widget_gallery.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ A simple text label.
121121
[Label reference](./widgets/label.md){ .md-button .md-button--primary }
122122

123123

124+
## Link
125+
126+
A clickable link that opens a URL.
127+
128+
[Link reference](./widgets/link.md){ .md-button .md-button--primary }
129+
130+
124131
## ListView
125132

126133
Display a list of items (items may be other widgets).

docs/widgets/link.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Link
2+
3+
!!! tip "Added in version 0.84.0"
4+
5+
A widget to display a piece of text that opens a URL when clicked, like a web browser link.
6+
7+
- [x] Focusable
8+
- [ ] Container
9+
10+
11+
## Example
12+
13+
A trivial app with a link.
14+
Clicking the link open's a web-browser—as you might expect!
15+
16+
=== "Output"
17+
18+
```{.textual path="docs/examples/widgets/link.py"}
19+
```
20+
21+
=== "link.py"
22+
23+
```python
24+
--8<-- "docs/examples/widgets/link.py"
25+
```
26+
27+
28+
## Reactive Attributes
29+
30+
| Name | Type | Default | Description |
31+
| ------ | ----- | ------- | ----------------------------------------- |
32+
| `text` | `str` | `""` | The text of the link. |
33+
| `url` | `str` | `""` | The URL to open when the link is clicked. |
34+
35+
36+
## Messages
37+
38+
This widget sends no messages.
39+
40+
## Bindings
41+
42+
The Link widget defines the following bindings:
43+
44+
::: textual.widgets.Link.BINDINGS
45+
options:
46+
show_root_heading: false
47+
show_root_toc_entry: false
48+
49+
50+
## Component classes
51+
52+
This widget contains no component classes.
53+
54+
55+
56+
---
57+
58+
59+
::: textual.widgets.Link
60+
options:
61+
heading_level: 2

docs/widgets/masked_input.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The example below shows a masked input to ease entering a credit card number.
1616
```{.textual path="docs/examples/widgets/masked_input.py"}
1717
```
1818

19-
=== "checkbox.py"
19+
=== "masked_input.py"
2020

2121
```python
2222
--8<-- "docs/examples/widgets/masked_input.py"

mkdocs-nav.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ nav:
152152
- "widgets/index.md"
153153
- "widgets/input.md"
154154
- "widgets/label.md"
155+
- "widgets/link.md"
155156
- "widgets/list_item.md"
156157
- "widgets/list_view.md"
157158
- "widgets/loading_indicator.md"
@@ -195,6 +196,7 @@ nav:
195196
- "api/filter.md"
196197
- "api/fuzzy_matcher.md"
197198
- "api/geometry.md"
199+
- "api/layout.md"
198200
- "api/lazy.md"
199201
- "api/logger.md"
200202
- "api/logging.md"

src/textual/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from textual.demo import DemoApp
1+
from textual.demo.demo_app import DemoApp
22

33
if __name__ == "__main__":
44
app = DemoApp()

src/textual/_arrange.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
from operator import attrgetter
66
from typing import TYPE_CHECKING, Iterable, Mapping, Sequence
77

8-
from textual._layout import DockArrangeResult, WidgetPlacement
98
from textual._partition import partition
109
from textual.geometry import Region, Size, Spacing
10+
from textual.layout import DockArrangeResult, WidgetPlacement
1111

1212
if TYPE_CHECKING:
1313
from textual.widget import Widget
@@ -90,7 +90,7 @@ def arrange(
9090

9191
if layout_widgets:
9292
# Arrange layout widgets (i.e. not docked)
93-
layout_placements = widget._layout.arrange(
93+
layout_placements = widget.layout.arrange(
9494
widget,
9595
layout_widgets,
9696
dock_region.size,

src/textual/_loop.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import Iterable, TypeVar
3+
from typing import Iterable, Literal, Sequence, TypeVar
44

55
T = TypeVar("T")
66

@@ -43,3 +43,44 @@ def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
4343
first = False
4444
previous_value = value
4545
yield first, True, previous_value
46+
47+
48+
def loop_from_index(
49+
values: Sequence[T],
50+
index: int,
51+
direction: Literal[-1, +1] = +1,
52+
wrap: bool = True,
53+
) -> Iterable[tuple[int, T]]:
54+
"""Iterate over values in a sequence from a given starting index, potentially wrapping the index
55+
if it would go out of bounds.
56+
57+
Note that the first value to be yielded is a step from `index`, and `index` will be yielded *last*.
58+
59+
60+
Args:
61+
values: A sequence of values.
62+
index: Starting index.
63+
direction: Direction to move index (+1 for forward, -1 for backward).
64+
bool: Should the index wrap when out of bounds?
65+
66+
Yields:
67+
A tuple of index and value from the sequence.
68+
"""
69+
# Sanity check for devs who miss the typing errors
70+
assert direction in (-1, +1), "direction must be -1 or +1"
71+
count = len(values)
72+
if wrap:
73+
for _ in range(count):
74+
index = (index + direction) % count
75+
yield (index, values[index])
76+
else:
77+
if direction == +1:
78+
for _ in range(count):
79+
if (index := index + 1) >= count:
80+
break
81+
yield (index, values[index])
82+
else:
83+
for _ in range(count):
84+
if (index := index - 1) < 0:
85+
break
86+
yield (index, values[index])

0 commit comments

Comments
 (0)