Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/3334.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Toga Web backend now supports the MultilineTextInput widget.
10 changes: 8 additions & 2 deletions docs/en/reference/api/widgets/multilinetextinput.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ A scrollable panel that allows for the display and editing of multiple lines of

///

/// tab | Web {{ not_supported }}
/// tab | Web

Not supported
![/reference/images/multilinetextinput-web.png](/reference/images/multilinetextinput-web.png){ width="300" }

/// caption

///

<!-- TODO: Update alt text -->

///

Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/data/widgets_by_platform.csv
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Divider,General Widget,[`Divider`][toga.Divider],A horizontal or vertical line,
ImageView,General Widget,[`ImageView`][toga.ImageView],A widget that displays an image,●,●,●,●,●,,
Label,General Widget,[`Label`][toga.Label],Text label,●,●,●,●,●,○,○
MapView,General Widget,[`MapView`][toga.MapView],A zoomable map that can be annotated with location pins,●,●,●,●,●,,
MultilineTextInput,General Widget,[`MultilineTextInput`][toga.MultilineTextInput],Multi-line Text Input field,●,●,●,●,●,,
MultilineTextInput,General Widget,[`MultilineTextInput`][toga.MultilineTextInput],Multi-line Text Input field,●,●,●,●,●,,
NumberInput,General Widget,[`NumberInput`][toga.NumberInput],A text input that is limited to numeric input,●,●,●,●,●,,
PasswordInput,General Widget,[`PasswordInput`][toga.PasswordInput],A text input that hides its input,●,●,●,●,●,○,
ProgressBar,General Widget,[`ProgressBar`][toga.ProgressBar],Progress Bar,●,●,●,●,●,○,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions web/src/toga_web/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
# from .widgets.detailedlist import DetailedList
# from .widgets.imageview import ImageView
from .widgets.label import Label
from .widgets.multilinetextinput import MultilineTextInput

# from .widgets.multilinetextinput import MultilineTextInput
# from .widgets.numberinput import NumberInput
# from .widgets.optioncontainer import OptionContainer
from .widgets.passwordinput import PasswordInput
Expand Down Expand Up @@ -69,7 +69,7 @@ def not_implemented(feature):
# 'DetailedList',
# 'ImageView',
"Label",
# 'MultilineTextInput',
"MultilineTextInput",
# 'NumberInput',
# 'OptionContainer',
"PasswordInput",
Expand Down
113 changes: 113 additions & 0 deletions web/src/toga_web/widgets/multilinetextinput.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from travertino.colors import TRANSPARENT
from travertino.size import at_least

from toga_web.libs import create_proxy

from .base import Widget


class MultilineTextInput(Widget):
def create(self):
self.native = self._create_native_widget("sl-textarea")
self.native.addEventListener("sl-input", create_proxy(self._on_input))

def _after_render(_):
self.native.shadowRoot.querySelector("textarea").style.resize = "none"

self.native.updateComplete.then(create_proxy(_after_render))

def _on_input(self, event):
self.interface.on_change()

def get_value(self):
return self.native.value or ""

def set_value(self, value):
self.native.value = "" if value is None else str(value)
self.interface.on_change()

def get_placeholder(self):
return self.native.placeholder or ""

def set_placeholder(self, value):
self.native.placeholder = value or ""

def get_readonly(self):
return bool(self.native.readonly)

def set_readonly(self, value):
self.native.readonly = bool(value)

def get_enabled(self):
return not bool(self.native.disabled)

def set_enabled(self, value):
self.native.disabled = not bool(value)

def _to_css_color(self, value: object) -> str:
if value is None or value is TRANSPARENT:
return ""
try:
return str(value)
except Exception:
return ""

def set_background_color(self, color):
css = self._to_css_color(color)

def _apply(_):
inner = self.native.shadowRoot.querySelector("textarea")
if inner:
if css:
inner.style.setProperty("background", css)
else:
inner.style.removeProperty("background")

self.native.updateComplete.then(create_proxy(_apply))

def set_color(self, color):
css = self._to_css_color(color)

def _apply(_):
inner = self.native.shadowRoot.querySelector("textarea")
if inner:
if css:
inner.style.setProperty("color", css)
else:
inner.style.removeProperty("color")

self.native.updateComplete.then(create_proxy(_apply))

def set_text_align(self, value):
mapping = {0: "left", 1: "right", 2: "center", 3: "justify"}
css_align = mapping.get(value, value if isinstance(value, str) else "left")

def _apply(_):
inner = self.native.shadowRoot.querySelector("textarea")
if inner:
inner.style.textAlign = css_align

self.native.updateComplete.then(create_proxy(_apply))

def set_font(self, font):
pass

def rehint(self):
self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH)
self.interface.intrinsic.height = at_least(self.interface._MIN_HEIGHT)

def scroll_to_top(self):
def _go(_):
inner = self.native.shadowRoot.querySelector("textarea")
if inner:
inner.scrollTop = 0

self.native.updateComplete.then(create_proxy(_go))

def scroll_to_bottom(self):
def _go(_):
inner = self.native.shadowRoot.querySelector("textarea")
if inner:
inner.scrollTop = max(0, inner.scrollHeight - inner.clientHeight)

self.native.updateComplete.then(create_proxy(_go))
Loading