streamlit-nightly 1.44.1.dev20250325__py3-none-any.whl → 1.44.1.dev20250327__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/config.py +1 -1
- streamlit/elements/lib/options_selector_utils.py +26 -5
- streamlit/elements/widgets/button_group.py +42 -11
- streamlit/elements/widgets/multiselect.py +166 -26
- streamlit/elements/widgets/selectbox.py +182 -38
- streamlit/proto/MetricsEvent_pb2.py +1 -1
- streamlit/proto/MetricsEvent_pb2.pyi +10 -10
- streamlit/proto/MultiSelect_pb2.py +4 -2
- streamlit/proto/MultiSelect_pb2.pyi +14 -2
- streamlit/proto/Selectbox_pb2.py +4 -2
- streamlit/proto/Selectbox_pb2.pyi +15 -2
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/{FileDownload.esm.B-UWWWdT.js → FileDownload.esm.jX-9l2Ep.js} +1 -1
- streamlit/static/static/js/{FileHelper.DRtEaIrj.js → FileHelper.aCeQQwv9.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.B_xJLRtH.js → FormClearHelper.CWUgHOqb.js} +1 -1
- streamlit/static/static/js/{Hooks.DtzWH7N2.js → Hooks.z6bpnOa4.js} +1 -1
- streamlit/static/static/js/{InputInstructions.CkYMcz8B.js → InputInstructions.CxNXqmaa.js} +1 -1
- streamlit/static/static/js/{ProgressBar.Drx8XNKv.js → ProgressBar.DeJx_v03.js} +1 -1
- streamlit/static/static/js/{RenderInPortalIfExists.BSkIOPqr.js → RenderInPortalIfExists.BzVEnQEP.js} +1 -1
- streamlit/static/static/js/{Toolbar.2r5jAJur.js → Toolbar.Buaxb3gQ.js} +1 -1
- streamlit/static/static/js/{base-input.CV_PJtfn.js → base-input.B02pchZb.js} +1 -1
- streamlit/static/static/js/{checkbox.CZjCrRM6.js → checkbox.BNevNWhL.js} +1 -1
- streamlit/static/static/js/{createSuper.wNbYZKaS.js → createSuper.HF1JI-bK.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.CxDiXQOm.js → data-grid-overlay-editor.DHpEpsQ_.js} +1 -1
- streamlit/static/static/js/{downloader.BKhc9vo-.js → downloader.B32k91dq.js} +1 -1
- streamlit/static/static/js/{es6.DMMA5jEp.js → es6.j4L3xv_m.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.rU5g5dG9.js → iframeResizer.contentWindow.DQOV--zq.js} +1 -1
- streamlit/static/static/js/{index.C8fLs5OO.js → index.1tdxODWC.js} +1 -1
- streamlit/static/static/js/{index.CdUwInkp.js → index.B2-yUxP6.js} +1 -1
- streamlit/static/static/js/{index.Ci8MtnNW.js → index.B28jf8c_.js} +15 -15
- streamlit/static/static/js/{index.CLpTIY8k.js → index.BBHrAwbG.js} +1 -1
- streamlit/static/static/js/{index.BxcYwJga.js → index.BDIF1v3E.js} +1 -1
- streamlit/static/static/js/{index.YSxp_z0H.js → index.BRDvEQpe.js} +1 -1
- streamlit/static/static/js/{index.hYp-Io20.js → index.BT-PT2u0.js} +1 -1
- streamlit/static/static/js/{index.CcHn1Jbf.js → index.BTG2J5Pk.js} +1 -1
- streamlit/static/static/js/{index.PUoEdeCj.js → index.BUz0sS-V.js} +1 -1
- streamlit/static/static/js/{index.60o7QeO8.js → index.BeuGcxG8.js} +1 -1
- streamlit/static/static/js/{index.wpF1efVn.js → index.BnYJb__c.js} +1 -1
- streamlit/static/static/js/{index.DkHRVwYA.js → index.C-GJaT09.js} +12 -12
- streamlit/static/static/js/{index.BnFXM4vg.js → index.C1B9TyzK.js} +1 -1
- streamlit/static/static/js/{index.B5181i5i.js → index.CDMGlkYx.js} +1 -1
- streamlit/static/static/js/{index.poiWcDhd.js → index.CExICAHy.js} +1 -1
- streamlit/static/static/js/{index.C7Pn7DNY.js → index.CKYXxi_d.js} +1 -1
- streamlit/static/static/js/index.CPMy5pwd.js +1 -0
- streamlit/static/static/js/{index.aJaKE3aW.js → index.CTgHTp02.js} +1 -1
- streamlit/static/static/js/{index.BGHyjPoE.js → index.CTwaWONb.js} +1 -1
- streamlit/static/static/js/{index.CjiDtsfR.js → index.CcMFXZBL.js} +1 -1
- streamlit/static/static/js/index.ChvqDLgw.js +1 -0
- streamlit/static/static/js/{index.lLHaP-WG.js → index.ClfebD_T.js} +1 -1
- streamlit/static/static/js/{index.COu5ow_u.js → index.CpDe9l-f.js} +1 -1
- streamlit/static/static/js/{index.Dh-GaoLL.js → index.Cq_L2WtW.js} +1 -1
- streamlit/static/static/js/{index.Cj6d-zQP.js → index.DBEif7dq.js} +2 -2
- streamlit/static/static/js/{index.BW1ps7Ve.js → index.DNURUtUa.js} +2 -2
- streamlit/static/static/js/{index.6LeN9cCn.js → index.DTDyF8nE.js} +1 -1
- streamlit/static/static/js/{index.B_lGEToe.js → index.DaJw5fna.js} +1 -1
- streamlit/static/static/js/{index.CY2a3G5f.js → index.DbqewZ6W.js} +1 -1
- streamlit/static/static/js/{index.C5tuUwoV.js → index.DfvKnm4Q.js} +1 -1
- streamlit/static/static/js/{index.BvR3oAAg.js → index.DsRxnb2z.js} +1 -1
- streamlit/static/static/js/index.Nb8G9oM-.js +1 -0
- streamlit/static/static/js/{index.B5pjbkNu.js → index.R8Go3XlF.js} +1 -1
- streamlit/static/static/js/{index.DmhhLL_7.js → index.Uid-bSyh.js} +1 -1
- streamlit/static/static/js/{index.BDnhO8N2.js → index.V3D0L00K.js} +1 -1
- streamlit/static/static/js/{index.C5x1XZ52.js → index.m0rRkw04.js} +1 -1
- streamlit/static/static/js/{index.qaTnk5v5.js → index.qkhdJyyt.js} +6 -6
- streamlit/static/static/js/{input.hfihv8cy.js → input.DogdK8Cg.js} +1 -1
- streamlit/static/static/js/{memory.CwFUs_rm.js → memory.B_1d0kyG.js} +1 -1
- streamlit/static/static/js/{mergeWith.NipycHTn.js → mergeWith.9h0p6sC_.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.BOZydn7S.js → number-overlay-editor.yRe6Yodu.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.BegGyJAz.js → possibleConstructorReturn.C73_6grg.js} +1 -1
- streamlit/static/static/js/{sandbox.UTLHU4Ul.js → sandbox.2u3nOS5d.js} +1 -1
- streamlit/static/static/js/{textarea.dlySzlub.js → textarea.DFCEFjUj.js} +1 -1
- streamlit/static/static/js/{timepicker.QFZeHTS5.js → timepicker.GuNna1EN.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.D1_QwNnQ.js → toConsumableArray.DARzcvE5.js} +1 -1
- streamlit/static/static/js/{uniqueId.DrcTmetR.js → uniqueId.fceb1ayN.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.jMbeuIfG.js → useBasicWidgetState.D6255-xX.js} +1 -1
- streamlit/static/static/js/{useOnInputChange.CsqSzHwu.js → useOnInputChange.BjnOKne4.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.Cnlc3o1n.js → withFullScreenWrapper.B7h9p1kI.js} +1 -1
- streamlit/testing/v1/element_tree.py +8 -3
- {streamlit_nightly-1.44.1.dev20250325.dist-info → streamlit_nightly-1.44.1.dev20250327.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.44.1.dev20250325.dist-info → streamlit_nightly-1.44.1.dev20250327.dist-info}/RECORD +84 -84
- streamlit/static/static/js/index.BKHYl8ma.js +0 -1
- streamlit/static/static/js/index.CEK_kfj1.js +0 -1
- streamlit/static/static/js/index.DMOnosvH.js +0 -1
- {streamlit_nightly-1.44.1.dev20250325.data → streamlit_nightly-1.44.1.dev20250327.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.44.1.dev20250325.dist-info → streamlit_nightly-1.44.1.dev20250327.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.44.1.dev20250325.dist-info → streamlit_nightly-1.44.1.dev20250327.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.44.1.dev20250325.dist-info → streamlit_nightly-1.44.1.dev20250327.dist-info}/top_level.txt +0 -0
streamlit/config.py
CHANGED
@@ -568,7 +568,7 @@ _create_option(
|
|
568
568
|
- False : This is deprecated. Streamlit displays "stacktrace"
|
569
569
|
error details.
|
570
570
|
""",
|
571
|
-
default_val=ShowErrorDetailsConfigOptions.FULL,
|
571
|
+
default_val=ShowErrorDetailsConfigOptions.FULL.value,
|
572
572
|
type_=str,
|
573
573
|
scriptable=True,
|
574
574
|
)
|
@@ -15,7 +15,7 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
from enum import Enum, EnumMeta
|
18
|
-
from typing import TYPE_CHECKING, Any, Final, TypeVar, overload
|
18
|
+
from typing import TYPE_CHECKING, Any, Callable, Final, TypeVar, overload
|
19
19
|
|
20
20
|
from streamlit import config, logger
|
21
21
|
from streamlit.dataframe_util import OptionSequence, convert_anything_to_list
|
@@ -211,10 +211,10 @@ def maybe_coerce_enum(register_widget_result, options, opt_sequence):
|
|
211
211
|
# (https://github.com/python/typing/issues/548)
|
212
212
|
@overload
|
213
213
|
def maybe_coerce_enum_sequence(
|
214
|
-
register_widget_result: RegisterWidgetResult[list[T]],
|
214
|
+
register_widget_result: RegisterWidgetResult[list[T] | list[T | str]],
|
215
215
|
options: OptionSequence[T],
|
216
216
|
opt_sequence: Sequence[T],
|
217
|
-
) -> RegisterWidgetResult[list[T]]: ...
|
217
|
+
) -> RegisterWidgetResult[list[T] | list[T | str]]: ...
|
218
218
|
|
219
219
|
|
220
220
|
@overload
|
@@ -227,8 +227,8 @@ def maybe_coerce_enum_sequence(
|
|
227
227
|
|
228
228
|
def maybe_coerce_enum_sequence(register_widget_result, options, opt_sequence):
|
229
229
|
"""Maybe Coerce a RegisterWidgetResult with a sequence of Enum members as value
|
230
|
-
to RegisterWidgetResult[Sequence[option]] if option is an EnumType, otherwise just
|
231
|
-
the original RegisterWidgetResult.
|
230
|
+
to RegisterWidgetResult[Sequence[option]] if option is an EnumType, otherwise just
|
231
|
+
return the original RegisterWidgetResult.
|
232
232
|
"""
|
233
233
|
|
234
234
|
# If not all widget values are Enums, return early
|
@@ -251,3 +251,24 @@ def maybe_coerce_enum_sequence(register_widget_result, options, opt_sequence):
|
|
251
251
|
),
|
252
252
|
register_widget_result.value_changed,
|
253
253
|
)
|
254
|
+
|
255
|
+
|
256
|
+
def create_mappings(
|
257
|
+
options: Sequence[T], format_func: Callable[[T], str] = str
|
258
|
+
) -> tuple[list[str], dict[str, int]]:
|
259
|
+
"""Iterates through the options and formats them using the format_func.
|
260
|
+
|
261
|
+
Returns a tuple of the formatted options and a mapping of the formatted options to
|
262
|
+
the original options.
|
263
|
+
"""
|
264
|
+
formatted_option_to_option_mapping: dict[str, int] = {}
|
265
|
+
formatted_options: list[str] = []
|
266
|
+
for index, option in enumerate(options):
|
267
|
+
formatted_option = format_func(option)
|
268
|
+
formatted_options.append(formatted_option)
|
269
|
+
formatted_option_to_option_mapping[formatted_option] = index
|
270
|
+
|
271
|
+
return (
|
272
|
+
formatted_options,
|
273
|
+
formatted_option_to_option_mapping,
|
274
|
+
)
|
@@ -15,6 +15,7 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
from collections.abc import Sequence
|
18
|
+
from dataclasses import dataclass, field
|
18
19
|
from typing import (
|
19
20
|
TYPE_CHECKING,
|
20
21
|
Any,
|
@@ -31,6 +32,7 @@ from typing_extensions import TypeAlias
|
|
31
32
|
|
32
33
|
from streamlit.elements.lib.form_utils import current_form_id
|
33
34
|
from streamlit.elements.lib.options_selector_utils import (
|
35
|
+
check_and_convert_to_indices,
|
34
36
|
convert_to_sequence_and_check_comparable,
|
35
37
|
get_default_indices,
|
36
38
|
)
|
@@ -46,7 +48,6 @@ from streamlit.elements.lib.utils import (
|
|
46
48
|
save_for_app_testing,
|
47
49
|
to_key,
|
48
50
|
)
|
49
|
-
from streamlit.elements.widgets.multiselect import MultiSelectSerde
|
50
51
|
from streamlit.errors import StreamlitAPIException
|
51
52
|
from streamlit.proto.ButtonGroup_pb2 import ButtonGroup as ButtonGroupProto
|
52
53
|
from streamlit.runtime.metrics_util import gather_metrics
|
@@ -89,9 +90,39 @@ _SELECTED_STAR_ICON: Final = ":material/star_filled:"
|
|
89
90
|
SelectionMode: TypeAlias = Literal["single", "multi"]
|
90
91
|
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
@dataclass
|
94
|
+
class _MultiSelectSerde(Generic[T]):
|
95
|
+
"""Only meant to be used internally for the button_group element.
|
96
|
+
|
97
|
+
This serde is inspired by the MultiSelectSerde from multiselect.py. That serde has
|
98
|
+
been updated since then to support the accept_new_options parameter, which is not
|
99
|
+
required by the button_group element. If this changes again at some point,
|
100
|
+
the two elements can share the same serde again.
|
101
|
+
"""
|
102
|
+
|
103
|
+
options: Sequence[T]
|
104
|
+
default_value: list[int] = field(default_factory=list)
|
105
|
+
|
106
|
+
def serialize(self, value: list[T]) -> list[int]:
|
107
|
+
indices = check_and_convert_to_indices(self.options, value)
|
108
|
+
return indices if indices is not None else []
|
109
|
+
|
110
|
+
def deserialize(
|
111
|
+
self,
|
112
|
+
ui_value: list[int] | None,
|
113
|
+
widget_id: str = "",
|
114
|
+
) -> list[T]:
|
115
|
+
current_value: list[int] = (
|
116
|
+
ui_value if ui_value is not None else self.default_value
|
117
|
+
)
|
118
|
+
return [self.options[i] for i in current_value]
|
119
|
+
|
120
|
+
|
121
|
+
class _SingleSelectSerde(Generic[T]):
|
122
|
+
"""Only meant to be used internally for the button_group element.
|
123
|
+
|
124
|
+
Uses the ButtonGroup's _MultiSelectSerde under-the-hood, but accepts a single
|
125
|
+
index value and deserializes to a single index value.
|
95
126
|
This is because button_group can be single and multi select, but we use the same
|
96
127
|
proto for both and, thus, map single values to a list of values and a receiving
|
97
128
|
value wrapped in a list to a single value.
|
@@ -106,7 +137,7 @@ class SingleSelectSerde(Generic[T]):
|
|
106
137
|
default_value: list[int] | None = None,
|
107
138
|
) -> None:
|
108
139
|
# see docstring about why we use MultiSelectSerde here
|
109
|
-
self.multiselect_serde:
|
140
|
+
self.multiselect_serde: _MultiSelectSerde[T] = _MultiSelectSerde(
|
110
141
|
option_indices, default_value if default_value is not None else []
|
111
142
|
)
|
112
143
|
|
@@ -123,7 +154,7 @@ class SingleSelectSerde(Generic[T]):
|
|
123
154
|
return deserialized[0]
|
124
155
|
|
125
156
|
|
126
|
-
class
|
157
|
+
class ButtonGroupSerde(Generic[T]):
|
127
158
|
"""A serde that can handle both single and multi select options.
|
128
159
|
|
129
160
|
It uses the same proto to wire the data, so that we can send and receive
|
@@ -142,10 +173,10 @@ class SingleOrMultiSelectSerde(Generic[T]):
|
|
142
173
|
self.options = options
|
143
174
|
self.default_values = default_values
|
144
175
|
self.type = type
|
145
|
-
self.serde:
|
146
|
-
|
176
|
+
self.serde: _SingleSelectSerde[T] | _MultiSelectSerde[T] = (
|
177
|
+
_SingleSelectSerde(options, default_value=default_values)
|
147
178
|
if type == "single"
|
148
|
-
else
|
179
|
+
else _MultiSelectSerde(options, default_values)
|
149
180
|
)
|
150
181
|
|
151
182
|
def serialize(self, value: T | list[T] | None) -> list[int]:
|
@@ -362,7 +393,7 @@ class ButtonGroupMixin:
|
|
362
393
|
f"The argument passed was '{options}'."
|
363
394
|
)
|
364
395
|
transformed_options, options_indices = get_mapped_options(options)
|
365
|
-
serde =
|
396
|
+
serde = _SingleSelectSerde[int](options_indices)
|
366
397
|
|
367
398
|
selection_visualization = ButtonGroupProto.SelectionVisualization.ONLY_SELECTED
|
368
399
|
if options == "stars":
|
@@ -857,7 +888,7 @@ class ButtonGroupMixin:
|
|
857
888
|
indexable_options = convert_to_sequence_and_check_comparable(options)
|
858
889
|
default_values = get_default_indices(indexable_options, default)
|
859
890
|
|
860
|
-
serde:
|
891
|
+
serde: ButtonGroupSerde[V] = ButtonGroupSerde[V](
|
861
892
|
indexable_options, default_values, selection_mode
|
862
893
|
)
|
863
894
|
|
@@ -14,15 +14,15 @@
|
|
14
14
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
|
-
from
|
17
|
+
from collections.abc import Sequence
|
18
18
|
from textwrap import dedent
|
19
|
-
from typing import TYPE_CHECKING, Any, Callable, Generic, cast
|
19
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, cast, overload
|
20
20
|
|
21
|
-
from streamlit.dataframe_util import OptionSequence
|
21
|
+
from streamlit.dataframe_util import OptionSequence, convert_anything_to_list
|
22
22
|
from streamlit.elements.lib.form_utils import current_form_id
|
23
23
|
from streamlit.elements.lib.options_selector_utils import (
|
24
|
-
check_and_convert_to_indices,
|
25
24
|
convert_to_sequence_and_check_comparable,
|
25
|
+
create_mappings,
|
26
26
|
get_default_indices,
|
27
27
|
maybe_coerce_enum_sequence,
|
28
28
|
)
|
@@ -62,24 +62,76 @@ if TYPE_CHECKING:
|
|
62
62
|
)
|
63
63
|
|
64
64
|
|
65
|
-
@dataclass
|
66
65
|
class MultiSelectSerde(Generic[T]):
|
67
66
|
options: Sequence[T]
|
68
|
-
|
67
|
+
formatted_options: list[str]
|
68
|
+
formatted_option_to_option_index: dict[str, int]
|
69
|
+
default_options_indices: list[int]
|
69
70
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
71
|
+
def __init__(
|
72
|
+
self,
|
73
|
+
options: Sequence[T],
|
74
|
+
*,
|
75
|
+
formatted_options: list[str],
|
76
|
+
formatted_option_to_option_index: dict[str, int],
|
77
|
+
default_options_indices: list[int] | None = None,
|
78
|
+
):
|
79
|
+
"""Initialize the MultiSelectSerde.
|
80
|
+
|
81
|
+
We do not store an option_to_formatted_option mapping because the generic
|
82
|
+
options might not be hashable, which would raise a RuntimeError. So we do
|
83
|
+
two lookups: option -> index -> formatted_option[index].
|
84
|
+
|
85
|
+
|
86
|
+
Parameters
|
87
|
+
----------
|
88
|
+
options : Sequence[T]
|
89
|
+
The sequence of selectable options.
|
90
|
+
formatted_options : list[str]
|
91
|
+
The string representations of each option. The formatted_options correspond
|
92
|
+
to the options sequence by index.
|
93
|
+
formatted_option_to_option_index : dict[str, int]
|
94
|
+
A mapping from formatted option strings to their corresponding indices in
|
95
|
+
the options sequence.
|
96
|
+
default_option_index : int or None, optional
|
97
|
+
The index of the default option to use when no selection is made.
|
98
|
+
If None, no default option is selected.
|
99
|
+
"""
|
100
|
+
|
101
|
+
self.options = options
|
102
|
+
self.formatted_options = formatted_options
|
103
|
+
self.formatted_option_to_option_index = formatted_option_to_option_index
|
104
|
+
self.default_options_indices = default_options_indices or []
|
105
|
+
|
106
|
+
def serialize(self, value: list[T | str] | list[T]) -> list[str]:
|
107
|
+
converted_value = convert_anything_to_list(value)
|
108
|
+
values: list[str] = []
|
109
|
+
for v in converted_value:
|
110
|
+
try:
|
111
|
+
option_index = self.options.index(v)
|
112
|
+
values.append(self.formatted_options[option_index])
|
113
|
+
except ValueError:
|
114
|
+
# at this point we know that v is a string, otherwise
|
115
|
+
# it would have been found in the options
|
116
|
+
values.append(cast("str", v))
|
117
|
+
return values
|
73
118
|
|
74
119
|
def deserialize(
|
75
120
|
self,
|
76
|
-
ui_value: list[
|
121
|
+
ui_value: list[str] | None,
|
77
122
|
widget_id: str = "",
|
78
|
-
) -> list[T]:
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
123
|
+
) -> list[T | str] | list[T]:
|
124
|
+
if ui_value is None:
|
125
|
+
return [self.options[i] for i in self.default_options_indices]
|
126
|
+
|
127
|
+
values: list[T | str] = []
|
128
|
+
for v in ui_value:
|
129
|
+
try:
|
130
|
+
option_index = self.formatted_options.index(v)
|
131
|
+
values.append(self.options[option_index])
|
132
|
+
except ValueError:
|
133
|
+
values.append(v)
|
134
|
+
return values
|
83
135
|
|
84
136
|
|
85
137
|
def _get_default_count(default: Sequence[Any] | Any | None) -> int:
|
@@ -104,13 +156,73 @@ def _check_max_selections(
|
|
104
156
|
|
105
157
|
|
106
158
|
class MultiSelectMixin:
|
159
|
+
@overload
|
160
|
+
def multiselect(
|
161
|
+
self,
|
162
|
+
label: str,
|
163
|
+
options: OptionSequence[T],
|
164
|
+
default: Any | None = None,
|
165
|
+
format_func: Callable[[Any], str] = str,
|
166
|
+
key: Key | None = None,
|
167
|
+
help: str | None = None,
|
168
|
+
on_change: WidgetCallback | None = None,
|
169
|
+
args: WidgetArgs | None = None,
|
170
|
+
kwargs: WidgetKwargs | None = None,
|
171
|
+
*, # keyword-only arguments:
|
172
|
+
max_selections: int | None = None,
|
173
|
+
placeholder: str | None = None,
|
174
|
+
disabled: bool = False,
|
175
|
+
label_visibility: LabelVisibility = "visible",
|
176
|
+
accept_new_options: Literal[False] = False,
|
177
|
+
) -> list[T]: ...
|
178
|
+
|
179
|
+
@overload
|
180
|
+
def multiselect(
|
181
|
+
self,
|
182
|
+
label: str,
|
183
|
+
options: OptionSequence[T],
|
184
|
+
default: Any | None = None,
|
185
|
+
format_func: Callable[[Any], str] = str,
|
186
|
+
key: Key | None = None,
|
187
|
+
help: str | None = None,
|
188
|
+
on_change: WidgetCallback | None = None,
|
189
|
+
args: WidgetArgs | None = None,
|
190
|
+
kwargs: WidgetKwargs | None = None,
|
191
|
+
*, # keyword-only arguments:
|
192
|
+
max_selections: int | None = None,
|
193
|
+
placeholder: str | None = None,
|
194
|
+
disabled: bool = False,
|
195
|
+
label_visibility: LabelVisibility = "visible",
|
196
|
+
accept_new_options: Literal[True] = True,
|
197
|
+
) -> list[T | str]: ...
|
198
|
+
|
199
|
+
@overload
|
200
|
+
def multiselect(
|
201
|
+
self,
|
202
|
+
label: str,
|
203
|
+
options: OptionSequence[T],
|
204
|
+
default: Any | None = None,
|
205
|
+
format_func: Callable[[Any], str] = str,
|
206
|
+
key: Key | None = None,
|
207
|
+
help: str | None = None,
|
208
|
+
on_change: WidgetCallback | None = None,
|
209
|
+
args: WidgetArgs | None = None,
|
210
|
+
kwargs: WidgetKwargs | None = None,
|
211
|
+
*, # keyword-only arguments:
|
212
|
+
max_selections: int | None = None,
|
213
|
+
placeholder: str | None = None,
|
214
|
+
disabled: bool = False,
|
215
|
+
label_visibility: LabelVisibility = "visible",
|
216
|
+
accept_new_options: bool = False,
|
217
|
+
) -> list[T] | list[T | str]: ...
|
218
|
+
|
107
219
|
@gather_metrics("multiselect")
|
108
220
|
def multiselect(
|
109
221
|
self,
|
110
222
|
label: str,
|
111
223
|
options: OptionSequence[T],
|
112
224
|
default: Any | None = None,
|
113
|
-
format_func: Callable[[Any],
|
225
|
+
format_func: Callable[[Any], str] = str,
|
114
226
|
key: Key | None = None,
|
115
227
|
help: str | None = None,
|
116
228
|
on_change: WidgetCallback | None = None,
|
@@ -118,10 +230,11 @@ class MultiSelectMixin:
|
|
118
230
|
kwargs: WidgetKwargs | None = None,
|
119
231
|
*, # keyword-only arguments:
|
120
232
|
max_selections: int | None = None,
|
121
|
-
placeholder: str
|
233
|
+
placeholder: str | None = None,
|
122
234
|
disabled: bool = False,
|
123
235
|
label_visibility: LabelVisibility = "visible",
|
124
|
-
|
236
|
+
accept_new_options: Literal[False, True] | bool = False,
|
237
|
+
) -> list[T] | list[T | str]:
|
125
238
|
r"""Display a multiselect widget.
|
126
239
|
The multiselect widget starts as empty.
|
127
240
|
|
@@ -190,9 +303,10 @@ class MultiSelectMixin:
|
|
190
303
|
max_selections: int
|
191
304
|
The max selections that can be selected at a time.
|
192
305
|
|
193
|
-
placeholder: str
|
306
|
+
placeholder: str | None
|
194
307
|
A string to display when no options are selected.
|
195
|
-
Defaults to "Choose an option."
|
308
|
+
Defaults to "Choose an option." or, if
|
309
|
+
``accept_new_options`` is set to ``True``, to "Choose or add an option".
|
196
310
|
|
197
311
|
disabled: bool
|
198
312
|
An optional boolean that disables the multiselect widget if set
|
@@ -204,6 +318,11 @@ class MultiSelectMixin:
|
|
204
318
|
label, which can help keep the widget alligned with other widgets.
|
205
319
|
If this is ``"collapsed"``, Streamlit displays no label or spacer.
|
206
320
|
|
321
|
+
accept_new_options: bool
|
322
|
+
If ``True``, the user can enter new options that don't exist in the
|
323
|
+
original options. The ``max_options`` argument is still enforced.
|
324
|
+
The default is ``False``.
|
325
|
+
|
207
326
|
Returns
|
208
327
|
-------
|
209
328
|
list
|
@@ -241,6 +360,7 @@ class MultiSelectMixin:
|
|
241
360
|
placeholder=placeholder,
|
242
361
|
disabled=disabled,
|
243
362
|
label_visibility=label_visibility,
|
363
|
+
accept_new_options=accept_new_options,
|
244
364
|
ctx=ctx,
|
245
365
|
)
|
246
366
|
|
@@ -257,11 +377,12 @@ class MultiSelectMixin:
|
|
257
377
|
kwargs: WidgetKwargs | None = None,
|
258
378
|
*, # keyword-only arguments:
|
259
379
|
max_selections: int | None = None,
|
260
|
-
placeholder: str
|
380
|
+
placeholder: str | None = None,
|
261
381
|
disabled: bool = False,
|
262
382
|
label_visibility: LabelVisibility = "visible",
|
383
|
+
accept_new_options: bool = False,
|
263
384
|
ctx: ScriptRunContext | None = None,
|
264
|
-
) -> list[T]:
|
385
|
+
) -> list[T] | list[T | str]:
|
265
386
|
key = to_key(key)
|
266
387
|
|
267
388
|
widget_name = "multiselect"
|
@@ -274,9 +395,19 @@ class MultiSelectMixin:
|
|
274
395
|
maybe_raise_label_warnings(label, label_visibility)
|
275
396
|
|
276
397
|
indexable_options = convert_to_sequence_and_check_comparable(options)
|
277
|
-
formatted_options =
|
398
|
+
formatted_options, formatted_option_to_option_index = create_mappings(
|
399
|
+
indexable_options, format_func
|
400
|
+
)
|
401
|
+
|
278
402
|
default_values = get_default_indices(indexable_options, default)
|
279
403
|
|
404
|
+
if placeholder is None:
|
405
|
+
placeholder = (
|
406
|
+
"Choose an option"
|
407
|
+
if not accept_new_options
|
408
|
+
else "Choose or add an option"
|
409
|
+
)
|
410
|
+
|
280
411
|
form_id = current_form_id(self.dg)
|
281
412
|
element_id = compute_and_register_element_id(
|
282
413
|
widget_name,
|
@@ -288,6 +419,7 @@ class MultiSelectMixin:
|
|
288
419
|
help=help,
|
289
420
|
max_selections=max_selections,
|
290
421
|
placeholder=placeholder,
|
422
|
+
accept_new_options=accept_new_options,
|
291
423
|
)
|
292
424
|
|
293
425
|
proto = MultiSelectProto()
|
@@ -304,8 +436,15 @@ class MultiSelectMixin:
|
|
304
436
|
proto.options[:] = formatted_options
|
305
437
|
if help is not None:
|
306
438
|
proto.help = dedent(help)
|
439
|
+
proto.accept_new_options = accept_new_options
|
440
|
+
|
441
|
+
serde = MultiSelectSerde(
|
442
|
+
indexable_options,
|
443
|
+
formatted_options=formatted_options,
|
444
|
+
formatted_option_to_option_index=formatted_option_to_option_index,
|
445
|
+
default_options_indices=default_values,
|
446
|
+
)
|
307
447
|
|
308
|
-
serde = MultiSelectSerde(indexable_options, default_values)
|
309
448
|
widget_state = register_widget(
|
310
449
|
proto.id,
|
311
450
|
on_change_handler=on_change,
|
@@ -314,16 +453,17 @@ class MultiSelectMixin:
|
|
314
453
|
deserializer=serde.deserialize,
|
315
454
|
serializer=serde.serialize,
|
316
455
|
ctx=ctx,
|
317
|
-
value_type="
|
456
|
+
value_type="string_array_value",
|
318
457
|
)
|
319
458
|
|
320
459
|
_check_max_selections(widget_state.value, max_selections)
|
460
|
+
|
321
461
|
widget_state = maybe_coerce_enum_sequence(
|
322
462
|
widget_state, options, indexable_options
|
323
463
|
)
|
324
464
|
|
325
465
|
if widget_state.value_changed:
|
326
|
-
proto.
|
466
|
+
proto.raw_values[:] = serde.serialize(widget_state.value)
|
327
467
|
proto.set_value = True
|
328
468
|
|
329
469
|
if ctx:
|