Skip to content

Commit

Permalink
feat: Numeric/tel keyboard for ui.textbox on mobile devices #2170 (#2194
Browse files Browse the repository at this point in the history
)
  • Loading branch information
marek-mihok authored Nov 16, 2023
1 parent a321fc9 commit 9897c98
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 5 deletions.
4 changes: 4 additions & 0 deletions py/examples/textbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ async def serve(q: Q):
ui.text(f'textbox_disabled_placeholder={q.args.textbox_disabled_placeholder}'),
ui.text(f'textbox_multiline={q.args.textbox_multiline}'),
ui.text(f'textbox_spellcheck_disabled={q.args.textbox_spellcheck_disabled}'),
ui.text(f'textbox_numeric={q.args.textbox_numeric}'),
ui.text(f'textbox_tel={q.args.textbox_tel}'),
ui.button(name='show_form', label='Back', primary=True),
]
else:
Expand All @@ -40,6 +42,8 @@ async def serve(q: Q):
placeholder='I am disabled'),
ui.textbox(name='textbox_multiline', label='Multiline textarea', multiline=True),
ui.textbox(name='textbox_spellcheck_disabled', label='Spellcheck disabled', spellcheck=False),
ui.textbox(name='textbox_numeric', label='With numeric keyboard (iOS, Android)', type='number'),
ui.textbox(name='textbox_tel', label='With telephone keyboard (iOS, Android)', type='tel'),
ui.button(name='show_inputs', label='Submit', primary=True),
])
await q.page.save()
19 changes: 19 additions & 0 deletions py/h2o_lightwave/h2o_lightwave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,15 @@ def load(__d: Dict) -> 'MessageBar':
)


_TextboxType = ['text', 'number', 'tel']


class TextboxType:
TEXT = 'text'
NUMBER = 'number'
TEL = 'tel'


class Textbox:
"""Create a text box.

Expand Down Expand Up @@ -998,6 +1007,7 @@ def __init__(
visible: Optional[bool] = None,
tooltip: Optional[str] = None,
spellcheck: Optional[bool] = None,
type: Optional[str] = None,
):
_guard_scalar('Textbox.name', name, (str,), True, False, False)
_guard_scalar('Textbox.label', label, (str,), False, True, False)
Expand All @@ -1019,6 +1029,7 @@ def __init__(
_guard_scalar('Textbox.visible', visible, (bool,), False, True, False)
_guard_scalar('Textbox.tooltip', tooltip, (str,), False, True, False)
_guard_scalar('Textbox.spellcheck', spellcheck, (bool,), False, True, False)
_guard_enum('Textbox.type', type, _TextboxType, True)
self.name = name
"""An identifying name for this component."""
self.label = label
Expand Down Expand Up @@ -1059,6 +1070,8 @@ def __init__(
"""An optional tooltip message displayed when a user clicks the help icon to the right of the component."""
self.spellcheck = spellcheck
"""True if the text may be checked for spelling errors. Defaults to True."""
self.type = type
"""Keyboard to be shown on mobile devices. Defaults to 'text'. One of 'text', 'number', 'tel'. See enum h2o_wave.ui.TextboxType."""

def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
Expand All @@ -1082,6 +1095,7 @@ def dump(self) -> Dict:
_guard_scalar('Textbox.visible', self.visible, (bool,), False, True, False)
_guard_scalar('Textbox.tooltip', self.tooltip, (str,), False, True, False)
_guard_scalar('Textbox.spellcheck', self.spellcheck, (bool,), False, True, False)
_guard_enum('Textbox.type', self.type, _TextboxType, True)
return _dump(
name=self.name,
label=self.label,
Expand All @@ -1103,6 +1117,7 @@ def dump(self) -> Dict:
visible=self.visible,
tooltip=self.tooltip,
spellcheck=self.spellcheck,
type=self.type,
)

@staticmethod
Expand Down Expand Up @@ -1148,6 +1163,8 @@ def load(__d: Dict) -> 'Textbox':
_guard_scalar('Textbox.tooltip', __d_tooltip, (str,), False, True, False)
__d_spellcheck: Any = __d.get('spellcheck')
_guard_scalar('Textbox.spellcheck', __d_spellcheck, (bool,), False, True, False)
__d_type: Any = __d.get('type')
_guard_enum('Textbox.type', __d_type, _TextboxType, True)
name: str = __d_name
label: Optional[str] = __d_label
placeholder: Optional[str] = __d_placeholder
Expand All @@ -1168,6 +1185,7 @@ def load(__d: Dict) -> 'Textbox':
visible: Optional[bool] = __d_visible
tooltip: Optional[str] = __d_tooltip
spellcheck: Optional[bool] = __d_spellcheck
type: Optional[str] = __d_type
return Textbox(
name,
label,
Expand All @@ -1189,6 +1207,7 @@ def load(__d: Dict) -> 'Textbox':
visible,
tooltip,
spellcheck,
type,
)


Expand Down
3 changes: 3 additions & 0 deletions py/h2o_lightwave/h2o_lightwave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ def textbox(
visible: Optional[bool] = None,
tooltip: Optional[str] = None,
spellcheck: Optional[bool] = None,
type: Optional[str] = None,
) -> Component:
"""Create a text box.
Expand Down Expand Up @@ -426,6 +427,7 @@ def textbox(
visible: True if the component should be visible. Defaults to True.
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
spellcheck: True if the text may be checked for spelling errors. Defaults to True.
type: Keyboard to be shown on mobile devices. Defaults to 'text'. One of 'text', 'number', 'tel'. See enum h2o_wave.ui.TextboxType.
Returns:
A `h2o_wave.types.Textbox` instance.
"""
Expand All @@ -450,6 +452,7 @@ def textbox(
visible,
tooltip,
spellcheck,
type,
))


Expand Down
19 changes: 19 additions & 0 deletions py/h2o_wave/h2o_wave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,15 @@ def load(__d: Dict) -> 'MessageBar':
)


_TextboxType = ['text', 'number', 'tel']


class TextboxType:
TEXT = 'text'
NUMBER = 'number'
TEL = 'tel'


class Textbox:
"""Create a text box.

Expand Down Expand Up @@ -998,6 +1007,7 @@ def __init__(
visible: Optional[bool] = None,
tooltip: Optional[str] = None,
spellcheck: Optional[bool] = None,
type: Optional[str] = None,
):
_guard_scalar('Textbox.name', name, (str,), True, False, False)
_guard_scalar('Textbox.label', label, (str,), False, True, False)
Expand All @@ -1019,6 +1029,7 @@ def __init__(
_guard_scalar('Textbox.visible', visible, (bool,), False, True, False)
_guard_scalar('Textbox.tooltip', tooltip, (str,), False, True, False)
_guard_scalar('Textbox.spellcheck', spellcheck, (bool,), False, True, False)
_guard_enum('Textbox.type', type, _TextboxType, True)
self.name = name
"""An identifying name for this component."""
self.label = label
Expand Down Expand Up @@ -1059,6 +1070,8 @@ def __init__(
"""An optional tooltip message displayed when a user clicks the help icon to the right of the component."""
self.spellcheck = spellcheck
"""True if the text may be checked for spelling errors. Defaults to True."""
self.type = type
"""Keyboard to be shown on mobile devices. Defaults to 'text'. One of 'text', 'number', 'tel'. See enum h2o_wave.ui.TextboxType."""

def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
Expand All @@ -1082,6 +1095,7 @@ def dump(self) -> Dict:
_guard_scalar('Textbox.visible', self.visible, (bool,), False, True, False)
_guard_scalar('Textbox.tooltip', self.tooltip, (str,), False, True, False)
_guard_scalar('Textbox.spellcheck', self.spellcheck, (bool,), False, True, False)
_guard_enum('Textbox.type', self.type, _TextboxType, True)
return _dump(
name=self.name,
label=self.label,
Expand All @@ -1103,6 +1117,7 @@ def dump(self) -> Dict:
visible=self.visible,
tooltip=self.tooltip,
spellcheck=self.spellcheck,
type=self.type,
)

@staticmethod
Expand Down Expand Up @@ -1148,6 +1163,8 @@ def load(__d: Dict) -> 'Textbox':
_guard_scalar('Textbox.tooltip', __d_tooltip, (str,), False, True, False)
__d_spellcheck: Any = __d.get('spellcheck')
_guard_scalar('Textbox.spellcheck', __d_spellcheck, (bool,), False, True, False)
__d_type: Any = __d.get('type')
_guard_enum('Textbox.type', __d_type, _TextboxType, True)
name: str = __d_name
label: Optional[str] = __d_label
placeholder: Optional[str] = __d_placeholder
Expand All @@ -1168,6 +1185,7 @@ def load(__d: Dict) -> 'Textbox':
visible: Optional[bool] = __d_visible
tooltip: Optional[str] = __d_tooltip
spellcheck: Optional[bool] = __d_spellcheck
type: Optional[str] = __d_type
return Textbox(
name,
label,
Expand All @@ -1189,6 +1207,7 @@ def load(__d: Dict) -> 'Textbox':
visible,
tooltip,
spellcheck,
type,
)


Expand Down
3 changes: 3 additions & 0 deletions py/h2o_wave/h2o_wave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ def textbox(
visible: Optional[bool] = None,
tooltip: Optional[str] = None,
spellcheck: Optional[bool] = None,
type: Optional[str] = None,
) -> Component:
"""Create a text box.
Expand Down Expand Up @@ -426,6 +427,7 @@ def textbox(
visible: True if the component should be visible. Defaults to True.
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
spellcheck: True if the text may be checked for spelling errors. Defaults to True.
type: Keyboard to be shown on mobile devices. Defaults to 'text'. One of 'text', 'number', 'tel'. See enum h2o_wave.ui.TextboxType.
Returns:
A `h2o_wave.types.Textbox` instance.
"""
Expand All @@ -450,6 +452,7 @@ def textbox(
visible,
tooltip,
spellcheck,
type,
))


Expand Down
9 changes: 7 additions & 2 deletions r/R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ ui_message_bar <- function(
#' @param visible True if the component should be visible. Defaults to True.
#' @param tooltip An optional tooltip message displayed when a user clicks the help icon to the right of the component.
#' @param spellcheck True if the text may be checked for spelling errors. Defaults to True.
#' @param type Keyboard to be shown on mobile devices. Defaults to 'text'.
#' One of 'text', 'number', 'tel'. See enum h2o_wave.ui.TextboxType.
#' @return A Textbox instance.
#' @export
ui_textbox <- function(
Expand All @@ -507,7 +509,8 @@ ui_textbox <- function(
width = NULL,
visible = NULL,
tooltip = NULL,
spellcheck = NULL) {
spellcheck = NULL,
type = NULL) {
.guard_scalar("name", "character", name)
.guard_scalar("label", "character", label)
.guard_scalar("placeholder", "character", placeholder)
Expand All @@ -528,6 +531,7 @@ ui_textbox <- function(
.guard_scalar("visible", "logical", visible)
.guard_scalar("tooltip", "character", tooltip)
.guard_scalar("spellcheck", "logical", spellcheck)
# TODO Validate type
.o <- list(textbox=list(
name=name,
label=label,
Expand All @@ -548,7 +552,8 @@ ui_textbox <- function(
width=width,
visible=visible,
tooltip=tooltip,
spellcheck=spellcheck))
spellcheck=spellcheck,
type=type))
class(.o) <- append(class(.o), c(.wave_obj, "WaveComponent"))
return(.o)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2620,7 +2620,7 @@
<option name="Python" value="true"/>
</context>
</template>
<template name="w_full_textbox" value="ui.textbox(name='$name$',label='$label$',placeholder='$placeholder$',value='$value$',mask='$mask$',icon='$icon$',prefix='$prefix$',suffix='$suffix$',error='$error$',required=$required$,disabled=$disabled$,readonly=$readonly$,multiline=$multiline$,password=$password$,trigger=$trigger$,height='$height$',width='$width$',visible=$visible$,tooltip='$tooltip$',spellcheck=$spellcheck$),$END$" description="Create Wave Textbox with full attributes." toReformat="true" toShortenFQNames="true">
<template name="w_full_textbox" value="ui.textbox(name='$name$',label='$label$',placeholder='$placeholder$',value='$value$',mask='$mask$',icon='$icon$',prefix='$prefix$',suffix='$suffix$',error='$error$',required=$required$,disabled=$disabled$,readonly=$readonly$,multiline=$multiline$,password=$password$,trigger=$trigger$,height='$height$',width='$width$',visible=$visible$,tooltip='$tooltip$',spellcheck=$spellcheck$,type='$type$'),$END$" description="Create Wave Textbox with full attributes." toReformat="true" toShortenFQNames="true">
<variable name="name" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="label" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="placeholder" expression="" defaultValue="" alwaysStopAt="true"/>
Expand All @@ -2641,6 +2641,7 @@
<variable name="visible" expression="" defaultValue="&quot;True&quot;" alwaysStopAt="true"/>
<variable name="tooltip" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="spellcheck" expression="" defaultValue="&quot;True&quot;" alwaysStopAt="true"/>
<variable name="type" expression="" defaultValue="&quot;text&quot;" alwaysStopAt="true"/>
<context>
<option name="Python" value="true"/>
</context>
Expand Down
2 changes: 1 addition & 1 deletion tools/vscode-extension/component-snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -1906,7 +1906,7 @@
"Wave Full Textbox": {
"prefix": "w_full_textbox",
"body": [
"ui.textbox(name='$1', label='$2', placeholder='$3', value='$4', mask='$5', icon='$6', prefix='$7', suffix='$8', error='$9', required=${10:False}, disabled=${11:False}, readonly=${12:False}, multiline=${13:False}, password=${14:False}, trigger=${15:False}, height='$16', width='${17:100%}', visible=${18:True}, tooltip='$19', spellcheck=${20:True}),$0"
"ui.textbox(name='$1', label='$2', placeholder='$3', value='$4', mask='$5', icon='$6', prefix='$7', suffix='$8', error='$9', required=${10:False}, disabled=${11:False}, readonly=${12:False}, multiline=${13:False}, password=${14:False}, trigger=${15:False}, height='$16', width='${17:100%}', visible=${18:True}, tooltip='$19', spellcheck=${20:True}, type='${21:text}'),$0"
],
"description": "Create a full Wave Textbox."
},
Expand Down
4 changes: 3 additions & 1 deletion ui/src/textbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export interface Textbox {
tooltip?: S
/** True if the text may be checked for spelling errors. Defaults to True. */
spellcheck?: B
/** Keyboard to be shown on mobile devices. Defaults to 'text'. */
type?: 'text' | 'number' | 'tel'
}

const DEBOUNCE_TIMEOUT = 500
Expand Down Expand Up @@ -96,7 +98,7 @@ export const
suffix: m.suffix,
multiline: m.multiline,
spellCheck: m.spellcheck,
type: m.password ? 'password' : undefined,
type: m.password ? 'password' : (m.type || 'text'),
}

React.useEffect(() => {
Expand Down
25 changes: 25 additions & 0 deletions website/widgets/form/textbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,28 @@ q.page['example'] = ui.form_card(
value="I have spellcheck disabld", spellcheck=False)]
)
```

## Mobile keyboard layout

Show proper keyboard layout on mobile devices with `type` attribute. Defaults to `text`.
This does not prevent user from typing any character.

```py
q.page['example'] = ui.form_card(
box='1 1 2 2',
items=[
ui.textbox(
name='textbox_keyboard_numeric',
label='With numeric keyboard (iOS, Android)',
# Show numeric keyboard
type='number'
),
ui.textbox(
name='textbox_keyboard_telephone',
label='With numeric keyboard (iOS, Android)',
# Show telephone keyboard
type='tel'
)
]
)
```

0 comments on commit 9897c98

Please sign in to comment.