sirfuzzalot / textual-inputs Goto Github PK
View Code? Open in Web Editor NEWTextual Inputs is a collection of input widgets for the Textual TUI framework ๐ก
License: MIT License
Textual Inputs is a collection of input widgets for the Textual TUI framework ๐ก
License: MIT License
Hey there,
First of all: this plugin looks / works great!
I'd like to see a field acting on file drag/drop / input, when using the current text field, this results in all path characters being inserted after one another ๐
What do you think? Could this be something worth it?
class TextInput(Widget):
"""
A simple text input widget based on https://github.com/sirfuzzalot/textual-inputs
Args:
name (Optional[str]): The unique name of the widget. If None, the widget will be automatically named.
value (str, optional): Defaults to "". The starting text value.
placeholder (str, optional), default="".
Text that appears in the widget when value is "" and the widget is not focused.
title (str, optional): Defaults to "". A title on the top left of the widget's border.
password (bool, optional): Defaults to False. Hides the text input, replacing it with bullets.
Attributes:
value (str): the value of the text field
placeholder (str): The placeholder message.
title (str): The displayed title of the widget.
has_password (bool): True if the text field masks the input.
has_focus (bool): True if the widget is focused.
cursor (Tuple[str, Style]): The character used for the cursor and a rich Style object defining its appearance.
Messages:
InputOnChange: Emitted when the contents of the input changes.
InputOnFocus: Emitted when the widget becomes focused.
Examples:
.. code-block:: python
from textual_inputs import TextInput
email_input = TextInput(
name="email",
placeholder="enter your email address...",
title="Email",
)
"""
value: Reactive[str] = Reactive("")
cursor: Tuple[str, Style] = ("|", Style(color="white", blink=True, bold=True))
_cursor_position: Reactive[int] = Reactive(0)
_has_focus: Reactive[bool] = Reactive(False)
def __init__(self, *, name: Optional[str] = None, value: str = "", placeholder: str = "",
title: str = "", password: bool = False, **kwargs: Any,) -> None:
super().__init__(name, **kwargs)
self.value = value
self.placeholder = placeholder
self.title = title
self.has_password = password
self._cursor_position = len(self.value)
def __rich_repr__(self):
yield from (("name", self.name), ("title", self.title), ("value", self._conceal_or_reveal(self.value)))
@property
def has_focus(self) -> bool:
"""Produces True if widget is focused"""
return self._has_focus
def render(self) -> RenderableType:
""" Produce a Panel object containing placeholder text or value and cursor. """
segments = self._render_text_with_cursor() if self.has_focus else\
[self._conceal_or_reveal(self.value)] if self.value else\
[self.title] if self.title and not self.placeholder else\
[self.placeholder]
text = Text.assemble(*segments)
title = "" if self.title and not any([self.placeholder, self.value, self.has_focus]) else self.title
return Panel(text, title=title, title_align="left", height=3, style=self.style or "",
border_style=self.border_style or Style(color="blue"),
box=rich.box.DOUBLE if self.has_focus else rich.box.SQUARE)
def _conceal_or_reveal(self, segment: str) -> str:
""" Produce the segment either concealed like a password or as it was passed. """
return "โข" * len(segment) if self.has_password else segment
def _render_text_with_cursor(self) -> List[Union[str, Tuple[str, Style]]]:
""" Produces the renderable Text object combining value and cursor """
return [self._conceal_or_reveal(self.value[: self._cursor_position]), self.cursor,
self._conceal_or_reveal(self.value[self._cursor_position+1:])]
async def on_focus(self, event: events.Focus) -> None:
self._has_focus = True
await self._emit_on_focus()
async def on_blur(self, event: events.Blur) -> None:
self._has_focus = False
async def on_key(self, event: events.Key) -> None:
if event.key == "left":
if self._cursor_position: self._cursor_position -= 1
elif event.key == "right":
if self._cursor_position <= len(self.value): self._cursor_position += 1
elif event.key == "home":
self._cursor_position = 0
elif event.key == "end":
self._cursor_position = len(self.value)
elif event.key == "ctrl+h": # Backspace
if self._cursor_position:
self.value = self.value[:self._cursor_position - 1] + self.value[self._cursor_position:]
self._cursor_position -= 1
await self._emit_on_change(event)
elif event.key == "delete":
if self._cursor_position <= len(self.value):
self.value = self.value[:self._cursor_position] + self.value[self._cursor_position+1:]
if self._cursor_position == len(self.value): self._cursor_position -= 1
await self._emit_on_change(event)
elif event.key in string.printable:
self.value = self.value[: self._cursor_position] + event.key + self.value[self._cursor_position :]
self._cursor_position += 1
await self._emit_on_change(event)
async def _emit_on_change(self, event: events.Key) -> None:
event.stop()
await self.emit(InputOnChange(self))
async def _emit_on_focus(self) -> None:
await self.emit(InputOnFocus(self))
Great project!
Something I would be interested in, is having syntax highlighting as you type, maybe using pygments.
Do you think it is feasible?
Would be good if we can customize the border type when its focused or not.
will be needing this to match the rounded borders on my Textual program
As a consumer I want to be able to override the following properties when initialising a new instance of an input class.
I feel it matches the project pretty well. I could personally work on this
USER STORY
As a developer I want clear guidance on how I may contribute my work to this project
REQUIREMENTS
datetime.datetime
as starting valuedatetime.datetime
objectUSER STORY
As a developer I want to get up and running on a contribution fast
REQUIREMENTS
Update modules to take advantage of PEP563 Postponed Evaluation of Annotations
from __future__ import annotations
Add publishing to PyPI Pipeline for new releases to making releasing faster.
Textual will introduce a breaking public API change soon as v0.2. We'll be pinning Textual Inputs to prevent breakage of the library while we update.
Passing arguments such as style="blue"
to the TextInput and IntegerInput constructor causes the Widget.init function to fail. This is because Widget
only supports the name
parameter.
IntegerInput should emit an on change message when the up and down arrows are used to increment or decrement the value.
textual-inputs/src/textual_inputs/integer_input.py
Lines 197 to 204 in 4c6a5b7
Using the example sample form, when you focus the Age field, if the first key you press is backspace (or cntr+h) the program will fail with the following error:
ValueError: invalid literal for int() with base 10: ''
Updating to textual 0.1.11 breaks custom message handlers by changing thier function prefixes from message_
to handle_
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.