diff --git a/docs/guide/actions.md b/docs/guide/actions.md index 517751450..b3fb64078 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -8,11 +8,11 @@ Action methods are methods on your app or widgets prefixed with `action_`. Aside !!! information - Action methods may be coroutines (methods with the `async` keyword). + Action methods may be coroutines (defined with the `async` keyword). Let's write an app with a simple action. -```python title="actions01.py" hl_lines="6-8" +```python title="actions01.py" hl_lines="6-8 12" --8<-- "docs/examples/guide/actions/actions01.py" ``` @@ -36,7 +36,7 @@ Action strings have a simple syntax, which for the most part replicates Python's !!! important - As much as they look like Python code, Textual does **not** call Python's `eval` function or similar to compile action strings. + As much as they *look* like Python code, Textual does **not** call Python's `eval` function or similar to compile action strings. Action strings have the following format: @@ -75,7 +75,7 @@ When you click any of the links, Textual runs the `"set_background"` action to c ## Bindings -Textual will also run actions that are bound to keys. The following example adds key [bindings](./input.md#bindings) for the ++r++, ++g++, and ++b++ keys which call the `"set_background"` action. +Textual will also run actions bound to keys. The following example adds key [bindings](./input.md#bindings) for the ++r++, ++g++, and ++b++ keys which call the `"set_background"` action. === "actions04.py" @@ -102,7 +102,7 @@ The following example defines a custom widget with its own `set_background` acti --8<-- "docs/examples/guide/actions/actions05.py" ``` -=== "actions05.py" +=== "actions05.sass" ```sass title="actions05.css" --8<-- "docs/examples/guide/actions/actions05.css" @@ -158,6 +158,3 @@ Textual supports the following builtin actions which are defined on the app. ### Quit ::: textual.app.App.action_quit - - -*TODO:* document more actions diff --git a/docs/guide/reactivity.md b/docs/guide/reactivity.md index 73edf48cb..5b4cf2af7 100644 --- a/docs/guide/reactivity.md +++ b/docs/guide/reactivity.md @@ -111,7 +111,7 @@ class MyWidget(Widget): ### Layout -The smart refresh feature will update the content area of a widget, but will not change its size. If modifying an attribute should change the size of the widget, you should set `layout=True` on the reactive attribute. This ensures that your CSS layout will update accordingly. +The smart refresh feature will update the content area of a widget, but will not change its size. If modifying an attribute should change the size of the widget, you can set `layout=True` on the reactive attribute. This ensures that your CSS layout will update accordingly. The following example modifies "refresh01.py" so that the greeting has an automatic width. @@ -138,9 +138,11 @@ If you type in to the input now, the greeting will expand to fit the content. If ## Validation -The next superpower we will look at is _validation_. If you add a method that begins with `validate_` followed by the name of your attribute, it will be called when you assign a value to that attribute. This method should accept the incoming value as a positional argument, and return the value to set (which may be the same or a different value). +The next superpower we will look at is _validation_, which can check and potentially modify a value you assign to a reactive attribute. -A common use for this is to restrict numbers to a given range. The following example keeps a count. There is a button to increase the count, and a button to decrease it. +If you add a method that begins with `validate_` followed by the name of your attribute, it will be called when you assign a value to that attribute. This method should accept the incoming value as a positional argument, and return the value to set (which may be the same or a different value). + +A common use for this is to restrict numbers to a given range. The following example keeps a count. There is a button to increase the count, and a button to decrease it. The validation ensures that the count will never go above 10 or below zero. === "validate01.py" @@ -159,7 +161,7 @@ A common use for this is to restrict numbers to a given range. The following exa ```{.textual path="docs/examples/guide/reactivity/validate01.py"} ``` -If you click the buttons in the above example it will show the current count. When `self.count` is modified in the button handler, Textual runs `validate_count` which limits self.count` to a maximum of 10, and stops it going below zero. +If you click the buttons in the above example it will show the current count. When `self.count` is modified in the button handler, Textual runs `validate_count` which performs the validation to limit the value of count. ## Watch methods @@ -218,8 +220,10 @@ The following example uses a computed attribute. It displays three inputs for th ```{.textual path="docs/examples/guide/reactivity/computed01.py"} ``` -Note the `compute_color` method which combines the color components into a [Color][textual.color.Color] object. When the _result_ of this method changes, Textual calls `watch_color` which uses the new color as a background. +Note the `compute_color` method which combines the color components into a [Color][textual.color.Color] object. It will be recalculated when any of the `red` , `green`, or `blue` attributes are modified. + +When the result of `compute_color` changes, Textual will also call `watch_color` since `color` still has the [watch method](#watch-methods) superpower. !!! note - You should avoid doing anything slow or cpu-intensive in a compute method. Textual calls compute methods on an object when _any_ reactive attribute changes, so it can known when it changes. + It is best to avoid doing anything slow or cpu-intensive in a compute method. Textual calls compute methods on an object when _any_ reactive attribute changes. diff --git a/src/textual/app.py b/src/textual/app.py index 5a1f7c1d0..9dcb85839 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1552,12 +1552,23 @@ class App(Generic[ReturnType], DOMNode): self.set_focus(node) async def action_switch_screen(self, screen: str) -> None: + """Switches to another screen. + + Args: + screen (str): Name of the screen. + """ self.switch_screen(screen) async def action_push_screen(self, screen: str) -> None: + """Pushes a screen on to the screen stack and makes it active. + + Args: + screen (str): Name of the screen. + """ self.push_screen(screen) async def action_pop_screen(self) -> None: + """Removes the topmost screen and makes the new topmost screen active.""" self.pop_screen() async def action_back(self) -> None: diff --git a/src/textual/widget.py b/src/textual/widget.py index 9b88fc745..70a3e4282 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -147,7 +147,7 @@ class Widget(DOMNode): """Widget may receive focus.""" can_focus_children: bool = True """Widget's children may receive focus.""" - expand = Reactive(True) + expand = Reactive(False) """Rich renderable may expand.""" shrink = Reactive(True) """Rich renderable may shrink.""" diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 8f3a4a4dc..616844bf2 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -79,13 +79,13 @@ class Input(Widget, can_focus=True): """ BINDINGS = [ - Binding("left", "cursor_left", "cursor left"), - Binding("right", "cursor_right", "cursor right"), - Binding("backspace", "delete_left", "delete left"), - Binding("home", "home", "home"), - Binding("end", "end", "end"), - Binding("ctrl+d", "delete_right", "delete right"), - Binding("enter", "submit", "Submit"), + Binding("left", "cursor_left", "cursor left", show=False), + Binding("right", "cursor_right", "cursor right", show=False), + Binding("backspace", "delete_left", "delete left", show=False), + Binding("home", "home", "home", show=False), + Binding("end", "end", "end", show=False), + Binding("ctrl+d", "delete_right", "delete right", show=False), + Binding("enter", "submit", "Submit", show=False), ] COMPONENT_CLASSES = {"input--cursor", "input--placeholder"} @@ -237,14 +237,14 @@ class Input(Widget, can_focus=True): # Do key bindings first if await self.handle_key(event): + event.prevent_default() event.stop() - elif event.key in ("tab", "shift+tab"): return elif event.is_printable: event.stop() assert event.char is not None self.insert_text_at_cursor(event.char) - event.prevent_default() + event.prevent_default() def on_paste(self, event: events.Paste) -> None: line = event.text.splitlines()[0] @@ -267,6 +267,11 @@ class Input(Widget, can_focus=True): self.cursor_position = len(self.value) def insert_text_at_cursor(self, text: str) -> None: + """Insert new text at the cursor, move the cursor to the end of the new text. + + Args: + text (str): new text to insert. + """ if self.cursor_position > len(self.value): self.value += text self.cursor_position = len(self.value)