8000 Add enter_pressed option to TextInput (#6593) · holoviz/panel@bec0f1d · GitHub
[go: up one dir, main page]

Skip to content

Commit bec0f1d

Browse files
authored
Add enter_pressed option to TextInput (#6593)
1 parent e6f658f commit bec0f1d

File tree

10 files changed

+149
-24
lines changed

10 files changed

+149
-24
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Releases
22

3+
## Version 1.5.0
4+
5+
### Breaking Changes
6+
7+
- `PasswordInput` and `TextAreaInput` no longer inherit directly from `TextInput` ([#6593](https://github.com/holoviz/panel/pull/6593))
8+
9+
310
## Version 1.4.2
411

512
Date: 2024-04-23

examples/reference/widgets/TextInput.ipynb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"\n",
2929
"* **``value``** (str): The current value u DC35 pdated when pressing the `<enter>` key or when the widget loses focus because the user clicks away or presses the tab key.\n",
3030
"* **``value_input``** (str): The current value updated on every key press.\n",
31+
"* **``enter_pressed``** (event): An event that triggers when the `<enter>` key is pressed.\n",
3132
"\n",
3233
"##### Display\n",
3334
"\n",

panel/chat/interface.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,7 @@ def _click_send(
413413
mime_type=active_widget.mime_type,
414414
file_name=active_widget.filename,
415415
)
416-
# don't use isinstance here; TextAreaInput subclasses TextInput
417-
if type(active_widget) is TextInput or self.reset_on_send:
416+
if isinstance(value, TextInput) or self.reset_on_send:
418417
updates = {"value": ""}
419418
if hasattr(active_widget, "value_input"):
420419
updates["value_input"] = ""

panel/models/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
from .trend import TrendIndicator # noqa
1919
from .widgets import ( # noqa
2020
Audio, Button, CheckboxButtonGroup, CustomSelect, FileDownload, Player,
21-
Progress, RadioButtonGroup, SingleSelect, TextAreaInput, TooltipIcon,
22-
Video, VideoStream,
21+
Progress, RadioButtonGroup, SingleSelect, TextAreaInput, TextInput,
22+
TooltipIcon, Video, VideoStream,
2323
)

panel/models/index.ts

Lines changed: 1 addition & 0 deletions
< DC2D /tr>
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export {State} from "./state"
3737
export {Tabs} from "./tabs"
3838
export {Terminal} from "./terminal"
3939
export {TextAreaInput} from "./textarea_input"
40+
export {TextInput} from "./text_input"
4041
export {TextToSpeech} from "./text_to_speech"
4142
export {ToggleIcon} from "./toggle_icon"
4243
export {TooltipIcon} from "./tooltip_icon"

panel/models/text_input.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {TextInput as BkTextInput, TextInputView as BkTextInputView} from "@bokehjs/models/widgets/text_input"
2+
import type * as p from "@bokehjs/core/properties"
3+
import {ModelEvent} from "@bokehjs/core/bokeh_events"
4+
import type {Attrs} from "@bokehjs/core/types"
5+
6+
export class EnterEvent extends ModelEvent {
7+
constructor(readonly value_input: string) {
8+
super()
9+
}
10+
11+
protected override get event_values(): Attrs {
12+
return {model: this.origin, value_input: this.value_input}
13+
}
14+
15+
static {
16+
this.prototype.event_name = "enter-pressed"
17+
}
18+
}
19+
20+
export class TextInputView extends BkTextInputView {
21+
declare model: TextInput
22+
23+
override _keyup(event: KeyboardEvent): void {
24+
super._keyup(event)
25+
if (event.key == "Enter") {
26+
const {value_input} = this.model
27+
this.model.trigger_event(new EnterEvent(value_input))
28+
}
29+
}
30+
}
31 2F93 +
32+
export namespace TextInput {
33+
export type Attrs = p.AttrsOf<Props>
34+
export type Props = BkTextInput.Props
35+
}
36+
37+
export interface TextInput extends TextInput.Attrs { }
38+
39+
export class TextInput extends BkTextInput {
40+
declare properties: TextInput.Props
41+
42+
constructor(attrs?: Partial<TextInput.Attrs>) {
43+
super(attrs)
44+
}
45+
46+
static override __module__ = "panel.models.widgets"
47+
48+
static {
49+
this.prototype.default_view = TextInputView
50+
51+
this.define<TextInput.Props>(({}) => ({ }))
52+
}
53+
}

panel/models/widgets.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
Any, Bool, Either, Enum, Float, Instance, Int, List, Nullable, Override,
77
String, Tuple,
88
)
9+
from bokeh.events import ModelEvent
910
from bokeh.models.ui import Tooltip
1011
from bokeh.models.ui.icons import Icon
1112
from bokeh.models.widgets import (
1213
Button as bkButton, CheckboxButtonGroup as bkCheckboxButtonGroup,
1314
InputWidget, RadioButtonGroup as bkRadioButtonGroup, Select,
14-
TextAreaInput as BkTextAreaInput, Widget,
15+
TextAreaInput as bkTextAreaInput, TextInput as bkTextInput, Widget,
1516
)
1617

1718
from .layout import HTMLBox
@@ -201,7 +202,7 @@ class TooltipIcon(Widget):
201202
)
202203

203204

204-
class TextAreaInput(BkTextAreaInput):
205+
class TextAreaInput(bkTextAreaInput):
205206

206207
auto_grow = Bool(
207208
default=False,
@@ -253,3 +254,19 @@ class RadioButtonGroup(bkRadioButtonGroup):
253254
Delay (in milliseconds) to display the tooltip after the cursor has
254255
hovered over the Button, default is 500ms.
255256
""")
257+
258+
259+
class EnterEvent(ModelEvent):
260+
261+
event_name = 'enter-pressed'
262+
263+
def __init__(self, model, value_input):
264+
self.value_input = value_input
265+
super().__init__(model=model)
266+
267+
def __repr__(self):
268+
return (
269+
f'{type(self).__name__}(value_input={self.value_input})'
270+
)
271+
272+
class TextInput(bkTextInput): ...

panel/tests/ui/layout/test_card.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,13 @@ def test_card_objects(page, card_components):
101101
card_header = card_elements.nth(0)
102102
w2_object = card_elements.nth(1)
103103
expect(card_header).to_have_class('card-header')
104-
expect(w2_object).to_have_class('bk-TextInput class_w2')
104+
expect(w2_object).to_have_class('bk-panel-models-widgets-TextInput class_w2')
105105

106106
w3 = TextInput(name='Text:', css_classes=['class_w3'])
107107
card.append(w3)
108108

109109
expect(card_elements).to_have_count(3)
110-
expect(card_elements.nth(2)).to_have_class('bk-TextInput class_w3')
110+
expect(card_elements.nth(2)).to_have_class('bk-panel-models-widgets-TextInput class_w3')
111111

112112

113113
def test_card_title(page, card_components):

panel/tests/ui/widgets/test_input.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
import numpy as np
44
import pytest
55

6+
import panel as pn
7+
68
pytest.importorskip("playwright")
79

810
from playwright.sync_api import expect
911

1012
from panel.tests.util import serve_component, wait_until
11-
from panel.widgets import DatetimePicker, DatetimeRangePicker, TextAreaInput
13+
from panel.widgets import (
14+
DatetimePicker, DatetimeRangePicker, TextAreaInput, TextInput,
15+
)
1216

1317
pytestmark = pytest.mark.ui
1418

@@ -698,3 +702,22 @@ def test_text_area_auto_grow_shrink_back_on_new_value(page):
698702
text_area.value = ""
699703

700704
expect(page.locator('.bk-input')).to_have_js_property('rows', 2)
705+
706+
def test_textinput_enter(page):
707+
text_input = TextInput()
708+
clicks = [0]
709+
710+
@pn.depends(text_input.param.enter_pressed, watch=True)
711+
def on_enter(event):
712+
clicks[0] += 1
713+
714+
serve_component(page, text_input)
715+
input_area = page.locator('.bk-input').first
716+
input_area.click()
717+
input_area.press('Enter')
718+
wait_until(lambda: clicks[0] == 1)
719+
720+
input_area.press("H")
721+
input_area.press("Enter")
722+
wait_until(lambda: clicks[0] == 2)
723+
assert text_input.value == "H"

panel/widgets/input.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@
2222
DatePicker as _BkDatePicker, DateRangePicker as _BkDateRangePicker,
2323
Div as _BkDiv, FileInput as _BkFileInput, NumericInput as _BkNumericInput,
2424
PasswordInput as _BkPasswordInput, Spinner as _BkSpinner,
25-
Switch as _BkSwitch, TextInput as _BkTextInput,
25+
Switch as _BkSwitch,
2626
)
2727

2828
from ..config import config
2929
from ..layout import Column, Panel
3030
from ..models import (
3131
DatetimePicker as _bkDatetimePicker, TextAreaInput as _bkTextAreaInput,
32+
TextInput as _BkTextInput,
3233
)
3334
from ..util import param_reprs, try_datetime64_to_datetime
3435
from .base import CompositeWidget, Widget
@@ -41,16 +42,7 @@
4142
from ..viewable import Viewable
4243

4344

44-
class TextInput(Widget):
45-
"""
46-
The `TextInput` widget allows entering any string using a text input box.
47-
48-
Reference: https://panel.holoviz.org/reference/widgets/TextInput.html
49-
50-
:Example:
51-
52-
>>> TextInput(name='Name', placeholder='Enter your name here ...')
53-
"""
45+
class _TextInputBase(Widget):
5446

5547
description = param.String(default=None, doc="""
5648
An HTML string describing the function of this component.""")
@@ -71,8 +63,6 @@ class TextInput(Widget):
7163
Width of this component. If sizing_mode is set to stretch
7264
or scale mode this will merely be used as a suggestion.""")
7365

74-
_widget_type: ClassVar[type[Model]] = _BkTextInput
75-
7666
@classmethod
7767
def from_param(cls, parameter: param.Parameter, onkeyup=False, **params) -> Viewable:
7868
"""
@@ -96,7 +86,41 @@ def from_param(cls, parameter: param.Parameter, onkeyup=False, **params) -> View
9686
return super().from_param(parameter, **params)
9787

9888

99-
class PasswordInput(TextInput):
89+
class TextInput(_TextInputBase):
90+
91+
"""
92+
The `TextInput` widget allows entering any string using a text input box.
93+
94+
Reference: https://panel.holoviz.org/reference/widgets/TextInput.html
95+
96+
:Example:
97+
98+
>>> TextInput(name='Name', placeholder='Enter your name here ...')
99+
"""
100+
101+
enter_pressed = param.Event(doc="""
102+
Event when the enter key has been pressed.""")
103+
104+
_widget_type: ClassVar[type[Model]] = _BkTextInput
105+
106+
_rename = {'enter_pressed': None}
107+
108+
def _get_model(
109+
self, doc: Document, root: Optional[Model] = None,
110+
parent: Optional[Model] = None, comm: Optional[Comm] = None
111+
) -> Model:
112+
model = super()._get_model(doc, root, parent, comm)
113+
self._register_events('enter-pressed', model=model, doc=doc, comm=comm)
114+
return model
115+
116+
def _process_event(self, event) -> None:
117+
if event.event_name == 'enter-pressed':
118+
self.value = event.value_input
119+
self.value_input = event.value_input
120+
self.enter_pressed = True
121+
122+
123+
class PasswordInput(_TextInputBase):
100124
"""
101125
The `PasswordInput` allows entering any string using an obfuscated text
102126
input box.
@@ -113,7 +137,7 @@ class PasswordInput(TextInput):
113137
_widget_type: ClassVar[type[Model]] = _BkPasswordInput
114138

115139

116-
class TextAreaInput(TextInput):
140+
class TextAreaInput(_TextInputBase):
117141
"""
118142
The `TextAreaInput` allows entering any multiline string using a text input
119143
box.

0 commit comments

Comments
 (0)
0