Skip to content

fix(input_select): Decouple input_select() and input_selectize() #1947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from 14 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED]

### New features

### Improvements

* `selectize`, `remove_button`, and `options` parameters of `ui.input_select()` have been deprecated; use `ui.input_selectize()` instead. (Thanks, @ErdaradunGaztea!) (#1947)

* Improved the styling and readability of markdown tables rendered by `ui.Chat()` and `ui.MarkdownStream()`. (#1973)

### Bug fixes


## [1.4.0] - 2025-04-08

## New features
Expand Down
97 changes: 87 additions & 10 deletions shiny/ui/_input_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from __future__ import annotations

from ..types import Jsonifiable
from .._deprecated import warn_deprecated
from ..types import Jsonifiable, MISSING_TYPE, DEPRECATED

__all__ = (
"input_select",
Expand Down Expand Up @@ -73,8 +74,9 @@ def input_selectize(
An input label.
choices
Either a list of choices or a dictionary mapping choice values to labels. Note
that if a dictionary is provided, the keys are used as the (input) values so
that the dictionary values can hold HTML labels. A dictionary of dictionaries is
that if a dictionary is provided, the keys are used as the (input) values and
the values are labels displayed to the user. It is not recommended to use
anything other than a string for these labels. A dictionary of dictionaries is
also supported, and in that case, the top-level keys are treated as
``<optgroup>`` labels.
selected
Expand Down Expand Up @@ -113,7 +115,7 @@ def input_selectize(
"""
resolved_id = resolve_id(id)

x = input_select(
x = _input_select_impl(
id=resolved_id,
label=label,
choices=restore_input(resolved_id, choices),
Expand All @@ -136,11 +138,11 @@ def input_select(
*,
selected: Optional[str | list[str]] = None,
multiple: bool = False,
selectize: bool = False,
selectize: bool | MISSING_TYPE = DEPRECATED,
width: Optional[str] = None,
size: Optional[str] = None,
remove_button: Optional[bool] = None,
options: Optional[dict[str, Jsonifiable | JSEval]] = None,
remove_button: Optional[bool] | MISSING_TYPE = DEPRECATED,
options: Optional[dict[str, Jsonifiable | JSEval]] | MISSING_TYPE = DEPRECATED,
) -> Tag:
"""
Create a select list that can be used to choose a single or multiple items from a
Expand All @@ -154,16 +156,17 @@ def input_select(
An input label.
choices
Either a list of choices or a dictionary mapping choice values to labels. Note
that if a dictionary is provided, the keys are used as the (input) values so
that the dictionary values can hold HTML labels. A dictionary of dictionaries is
that if a dictionary is provided, the keys are used as the (input) values and
the values are labels displayed to the user. It is not recommended to use
anything other than a string for these labels. A dictionary of dictionaries is
also supported, and in that case, the top-level keys are treated as
``<optgroup>`` labels.
selected
The values that should be initially selected, if any.
multiple
Is selection of multiple items allowed?
selectize
Whether to use selectize.js or not.
Deprecated. Use ``input_selectize()`` instead of passing ``selectize=True``.
width
The CSS width, e.g. '400px', or '100%'
size
Expand Down Expand Up @@ -192,6 +195,61 @@ def input_select(
* :func:`~shiny.ui.input_radio_buttons`
* :func:`~shiny.ui.input_checkbox_group`
"""
if isinstance(selectize, MISSING_TYPE):
selectize = False
else:
warn_deprecated(
"`selectize` parameter of `input_select()` is deprecated. "
"Use `input_selectize()` instead of passing `selectize=True`."
)

if isinstance(remove_button, MISSING_TYPE):
remove_button = None
else:
warn_deprecated(
"`remove_button` parameter of `input_select()` is deprecated. "
"Use `input_selectize()` instead."
)

if isinstance(options, MISSING_TYPE):
options = None
else:
warn_deprecated(
"`options` parameter of `input_select()` is deprecated. "
"Use `input_selectize()` instead."
)

resolved_id = resolve_id(id)

x = _input_select_impl(
id=resolved_id,
label=label,
choices=choices,
selected=selected,
multiple=multiple,
selectize=selectize,
width=width,
size=size,
remove_button=remove_button,
options=options,
)

return x


def _input_select_impl(
id: str,
label: TagChild,
choices: SelectChoicesArg,
*,
selected: Optional[str | list[str]] = None,
multiple: bool = False,
selectize: bool = False,
width: Optional[str] = None,
size: Optional[str] = None,
remove_button: Optional[bool] = None,
options: Optional[dict[str, Jsonifiable | JSEval]] = None,
) -> Tag:
if options is not None and selectize is False:
raise Exception("Options can only be set when selectize is `True`.")

Expand All @@ -201,6 +259,12 @@ def input_select(

choices_ = _normalize_choices(choices)

if _contains_html(choices_):
warn_deprecated(
"Passing anything other than a string to `choices` parameter of "
"`input_select()` and `input_selectize()` is deprecated."
)

selected = restore_input(resolved_id, selected)
if selected is None and not multiple:
selected = _find_first_option(choices_)
Expand Down Expand Up @@ -282,6 +346,19 @@ def _normalize_choices(x: SelectChoicesArg) -> _SelectChoices:
return x


def _contains_html(x: _SelectChoices) -> bool:
for v in x.values():
if isinstance(v, Mapping):
# Check the `_Choices` values of `_OptGrpChoices`
for vv in v.values():
if not isinstance(vv, str):
return True
else:
if not isinstance(v, str):
return True
return False


def _render_choices(
x: _SelectChoices, selected: Optional[str | list[str]] = None
) -> TagList:
Expand Down
Loading