streamlit 1.50.0__py3-none-any.whl → 1.51.0__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/__init__.py +4 -1
- streamlit/commands/navigation.py +4 -6
- streamlit/commands/page_config.py +4 -6
- streamlit/components/v2/__init__.py +458 -0
- streamlit/components/v2/bidi_component/__init__.py +20 -0
- streamlit/components/v2/bidi_component/constants.py +29 -0
- streamlit/components/v2/bidi_component/main.py +386 -0
- streamlit/components/v2/bidi_component/serialization.py +265 -0
- streamlit/components/v2/bidi_component/state.py +92 -0
- streamlit/components/v2/component_definition_resolver.py +143 -0
- streamlit/components/v2/component_file_watcher.py +403 -0
- streamlit/components/v2/component_manager.py +431 -0
- streamlit/components/v2/component_manifest_handler.py +122 -0
- streamlit/components/v2/component_path_utils.py +245 -0
- streamlit/components/v2/component_registry.py +409 -0
- streamlit/components/v2/get_bidi_component_manager.py +51 -0
- streamlit/components/v2/manifest_scanner.py +615 -0
- streamlit/components/v2/presentation.py +198 -0
- streamlit/components/v2/types.py +324 -0
- streamlit/config.py +456 -53
- streamlit/config_option.py +4 -1
- streamlit/config_util.py +650 -1
- streamlit/dataframe_util.py +15 -8
- streamlit/delta_generator.py +6 -4
- streamlit/delta_generator_singletons.py +3 -1
- streamlit/deprecation_util.py +17 -6
- streamlit/elements/arrow.py +37 -9
- streamlit/elements/deck_gl_json_chart.py +97 -39
- streamlit/elements/dialog_decorator.py +2 -1
- streamlit/elements/exception.py +3 -1
- streamlit/elements/graphviz_chart.py +1 -3
- streamlit/elements/heading.py +3 -5
- streamlit/elements/image.py +2 -4
- streamlit/elements/layouts.py +31 -11
- streamlit/elements/lib/built_in_chart_utils.py +1 -3
- streamlit/elements/lib/color_util.py +8 -18
- streamlit/elements/lib/column_config_utils.py +4 -8
- streamlit/elements/lib/column_types.py +40 -12
- streamlit/elements/lib/dialog.py +2 -2
- streamlit/elements/lib/image_utils.py +3 -5
- streamlit/elements/lib/layout_utils.py +50 -13
- streamlit/elements/lib/mutable_status_container.py +2 -2
- streamlit/elements/lib/options_selector_utils.py +2 -2
- streamlit/elements/lib/utils.py +4 -4
- streamlit/elements/map.py +80 -37
- streamlit/elements/media.py +5 -7
- streamlit/elements/metric.py +3 -5
- streamlit/elements/pdf.py +2 -4
- streamlit/elements/plotly_chart.py +125 -17
- streamlit/elements/progress.py +2 -4
- streamlit/elements/space.py +113 -0
- streamlit/elements/vega_charts.py +339 -148
- streamlit/elements/widgets/audio_input.py +5 -5
- streamlit/elements/widgets/button.py +2 -4
- streamlit/elements/widgets/button_group.py +33 -7
- streamlit/elements/widgets/camera_input.py +2 -4
- streamlit/elements/widgets/chat.py +7 -1
- streamlit/elements/widgets/color_picker.py +1 -1
- streamlit/elements/widgets/data_editor.py +28 -24
- streamlit/elements/widgets/file_uploader.py +5 -10
- streamlit/elements/widgets/multiselect.py +4 -3
- streamlit/elements/widgets/number_input.py +2 -4
- streamlit/elements/widgets/radio.py +10 -3
- streamlit/elements/widgets/select_slider.py +8 -5
- streamlit/elements/widgets/selectbox.py +6 -3
- streamlit/elements/widgets/slider.py +38 -42
- streamlit/elements/widgets/time_widgets.py +6 -12
- streamlit/elements/write.py +27 -6
- streamlit/emojis.py +1 -1
- streamlit/errors.py +115 -0
- streamlit/hello/hello.py +8 -0
- streamlit/hello/utils.py +2 -1
- streamlit/material_icon_names.py +1 -1
- streamlit/navigation/page.py +4 -1
- streamlit/proto/ArrowData_pb2.py +27 -0
- streamlit/proto/ArrowData_pb2.pyi +46 -0
- streamlit/proto/BidiComponent_pb2.py +34 -0
- streamlit/proto/BidiComponent_pb2.pyi +153 -0
- streamlit/proto/Block_pb2.py +7 -7
- streamlit/proto/Block_pb2.pyi +4 -1
- streamlit/proto/DeckGlJsonChart_pb2.py +10 -4
- streamlit/proto/DeckGlJsonChart_pb2.pyi +9 -3
- streamlit/proto/Element_pb2.py +5 -3
- streamlit/proto/Element_pb2.pyi +14 -4
- streamlit/proto/HeightConfig_pb2.py +2 -2
- streamlit/proto/HeightConfig_pb2.pyi +6 -3
- streamlit/proto/NewSession_pb2.py +18 -18
- streamlit/proto/NewSession_pb2.pyi +25 -6
- streamlit/proto/PlotlyChart_pb2.py +8 -6
- streamlit/proto/PlotlyChart_pb2.pyi +3 -1
- streamlit/proto/Space_pb2.py +27 -0
- streamlit/proto/Space_pb2.pyi +42 -0
- streamlit/proto/WidgetStates_pb2.py +2 -2
- streamlit/proto/WidgetStates_pb2.pyi +13 -3
- streamlit/proto/WidthConfig_pb2.py +2 -2
- streamlit/proto/WidthConfig_pb2.pyi +6 -3
- streamlit/runtime/app_session.py +27 -1
- streamlit/runtime/caching/cache_data_api.py +4 -4
- streamlit/runtime/caching/cache_errors.py +4 -1
- streamlit/runtime/caching/cache_resource_api.py +3 -2
- streamlit/runtime/caching/cache_utils.py +2 -1
- streamlit/runtime/caching/cached_message_replay.py +3 -3
- streamlit/runtime/caching/hashing.py +3 -4
- streamlit/runtime/caching/legacy_cache_api.py +2 -1
- streamlit/runtime/connection_factory.py +1 -3
- streamlit/runtime/forward_msg_queue.py +4 -1
- streamlit/runtime/fragment.py +2 -1
- streamlit/runtime/memory_media_file_storage.py +1 -1
- streamlit/runtime/metrics_util.py +6 -2
- streamlit/runtime/runtime.py +14 -0
- streamlit/runtime/scriptrunner/exec_code.py +2 -1
- streamlit/runtime/scriptrunner/script_runner.py +2 -2
- streamlit/runtime/scriptrunner_utils/script_run_context.py +3 -6
- streamlit/runtime/secrets.py +2 -4
- streamlit/runtime/session_manager.py +3 -1
- streamlit/runtime/state/common.py +30 -5
- streamlit/runtime/state/presentation.py +85 -0
- streamlit/runtime/state/safe_session_state.py +2 -2
- streamlit/runtime/state/session_state.py +220 -16
- streamlit/runtime/state/widgets.py +19 -3
- streamlit/runtime/websocket_session_manager.py +3 -1
- streamlit/source_util.py +2 -2
- streamlit/static/index.html +2 -2
- streamlit/static/manifest.json +243 -226
- streamlit/static/static/css/{index.CIiu7Ygf.css → index.BpABIXK9.css} +1 -1
- streamlit/static/static/css/index.DgR7E2CV.css +1 -0
- streamlit/static/static/js/{ErrorOutline.esm.DUpR0_Ka.js → ErrorOutline.esm.YoJdlW1p.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.CN4j9-1w.js → FileDownload.esm.Ddx8VEYy.js} +1 -1
- streamlit/static/static/js/{FileHelper.CaIUKG91.js → FileHelper.90EtOmj9.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.DTcdrasw.js → FormClearHelper.BB1Km6eP.js} +1 -1
- streamlit/static/static/js/InputInstructions.jhH15PqV.js +1 -0
- streamlit/static/static/js/{Particles.CElH0XX2.js → Particles.DUsputn1.js} +1 -1
- streamlit/static/static/js/{ProgressBar.DetlP5aY.js → ProgressBar.DLY8H6nE.js} +1 -1
- streamlit/static/static/js/{Toolbar.C77ar7rq.js → Toolbar.D8nHCkuz.js} +1 -1
- streamlit/static/static/js/{base-input.BQft14La.js → base-input.CJGiNqed.js} +3 -3
- streamlit/static/static/js/{checkbox.yZOfXCeX.js → checkbox.Cpdd482O.js} +1 -1
- streamlit/static/static/js/{createSuper.Dh9w1cs8.js → createSuper.CuQIogbW.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.DcuHuCyW.js → data-grid-overlay-editor.2Ufgxc6y.js} +1 -1
- streamlit/static/static/js/{downloader.MeHtkq8r.js → downloader.CN0K7xlu.js} +1 -1
- streamlit/static/static/js/{es6.VpBPGCnM.js → es6.BJcsVXQ0.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.yMw_ARIL.js → iframeResizer.contentWindow.XzUvQqcZ.js} +1 -1
- streamlit/static/static/js/index.B1ZQh4P1.js +1 -0
- streamlit/static/static/js/index.BKstZk0M.js +27 -0
- streamlit/static/static/js/{index.Cnpi3o3E.js → index.BMcFsUee.js} +1 -1
- streamlit/static/static/js/{index.DKv_lNO7.js → index.BR-IdcTb.js} +1 -1
- streamlit/static/static/js/{index.FFOzOWzC.js → index.B_dWA3vd.js} +1 -1
- streamlit/static/static/js/{index.Bj9JgOEC.js → index.BgnZEMVh.js} +1 -1
- streamlit/static/static/js/{index.Bxz2yX3P.js → index.BohqXifI.js} +1 -1
- streamlit/static/static/js/{index.Dbe-Q3C-.js → index.Br5nxKNj.js} +1 -1
- streamlit/static/static/js/{index.BjCwMzj4.js → index.BrIKVbNc.js} +2 -2
- streamlit/static/static/js/index.BtWUPzle.js +1 -0
- streamlit/static/static/js/{index.CGYqqs6j.js → index.C0RLraek.js} +1 -1
- streamlit/static/static/js/{index.D2QEXQq_.js → index.CAIjskgG.js} +1 -1
- streamlit/static/static/js/{index.6xX1278W.js → index.CAj-7vWz.js} +131 -157
- streamlit/static/static/js/{index.DK7hD7_w.js → index.CMtEit2O.js} +1 -1
- streamlit/static/static/js/{index.DNLrMXgm.js → index.CkRlykEE.js} +1 -1
- streamlit/static/static/js/{index.ClELlchS.js → index.CmN3FXfI.js} +1 -1
- streamlit/static/static/js/{index.GRUzrudl.js → index.CwbFI1_-.js} +1 -1
- streamlit/static/static/js/{index.Ctn27_AE.js → index.CxIUUfab.js} +27 -27
- streamlit/static/static/js/index.D2KPNy7e.js +1 -0
- streamlit/static/static/js/{index.B0H9IXUJ.js → index.D3GPA5k4.js} +3 -3
- streamlit/static/static/js/{index.BycLveZ4.js → index.DGAh7DMq.js} +1 -1
- streamlit/static/static/js/index.DKb_NvmG.js +197 -0
- streamlit/static/static/js/{index.BPQo7BKk.js → index.DMqgUYKq.js} +1 -1
- streamlit/static/static/js/{index.CH1tqnSs.js → index.DOFlg3dS.js} +1 -1
- streamlit/static/static/js/{index.64ejlaaT.js → index.DPUXkcQL.js} +1 -1
- streamlit/static/static/js/{index.B-hiXRzw.js → index.DX1xY89g.js} +1 -1
- streamlit/static/static/js/index.DYATBCsq.js +2 -0
- streamlit/static/static/js/{index.DHh-U0dK.js → index.DaSmGJ76.js} +3 -3
- streamlit/static/static/js/{index.DuxqVQpd.js → index.Dd7bMeLP.js} +1 -1
- streamlit/static/static/js/{index.B4cAbHP6.js → index.DjmmgI5U.js} +1 -1
- streamlit/static/static/js/{index.DcPNYEUo.js → index.Dq56CyM2.js} +1 -1
- streamlit/static/static/js/{index.CiAQIz1H.js → index.DuiXaS5_.js} +1 -1
- streamlit/static/static/js/index.DvFidMLe.js +2 -0
- streamlit/static/static/js/{index.C9BdUqTi.js → index.DwkhC5Pc.js} +1 -1
- streamlit/static/static/js/{index.B4dUQfni.js → index.Q-3sFn1v.js} +1 -1
- streamlit/static/static/js/{index.CMItVsFA.js → index.QJ5QO9sJ.js} +1 -1
- streamlit/static/static/js/{index.CTBk8Vk2.js → index.VwTaeety.js} +1 -1
- streamlit/static/static/js/{index.Ck8rQ9OL.js → index.YOqQbeX8.js} +1 -1
- streamlit/static/static/js/{input.s6pjQ49A.js → input.D4MN_FzN.js} +1 -1
- streamlit/static/static/js/{memory.Cuvsdfrl.js → memory.DrZjtdGT.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.DdgVR5m3.js → number-overlay-editor.DRwAw1In.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.CqidKeei.js → possibleConstructorReturn.exeeJQEP.js} +1 -1
- streamlit/static/static/js/record.B-tDciZb.js +1 -0
- streamlit/static/static/js/{sandbox.CCQREcJx.js → sandbox.ClO3IuUr.js} +1 -1
- streamlit/static/static/js/{timepicker.mkJF97Bb.js → timepicker.DAhu-vcF.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.De7I7KVR.js → toConsumableArray.DNbljYEC.js} +1 -1
- streamlit/static/static/js/{uniqueId.RI1LJdtz.js → uniqueId.oG4Gvj1v.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.CedkNjUW.js → useBasicWidgetState.D6sOH6oI.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.Ca7w8dVs.js → useTextInputAutoExpand.4u3_GcuN.js} +1 -1
- streamlit/static/static/js/{useUpdateUiValue.DeXelfRH.js → useUpdateUiValue.F2R3eTeR.js} +1 -1
- streamlit/static/static/js/wavesurfer.esm.vI8Eid4k.js +73 -0
- streamlit/static/static/js/{withFullScreenWrapper.C3561XxJ.js → withFullScreenWrapper.zothJIsI.js} +1 -1
- streamlit/static/static/media/MaterialSymbols-Rounded.C7IFxh57.woff2 +0 -0
- streamlit/string_util.py +1 -3
- streamlit/testing/v1/app_test.py +2 -2
- streamlit/testing/v1/element_tree.py +23 -9
- streamlit/testing/v1/util.py +2 -2
- streamlit/type_util.py +3 -4
- streamlit/url_util.py +1 -3
- streamlit/user_info.py +1 -2
- streamlit/util.py +3 -1
- streamlit/watcher/event_based_path_watcher.py +23 -12
- streamlit/watcher/local_sources_watcher.py +11 -1
- streamlit/watcher/path_watcher.py +9 -6
- streamlit/watcher/polling_path_watcher.py +4 -1
- streamlit/watcher/util.py +2 -2
- streamlit/web/cli.py +51 -22
- streamlit/web/server/bidi_component_request_handler.py +193 -0
- streamlit/web/server/component_file_utils.py +97 -0
- streamlit/web/server/component_request_handler.py +8 -21
- streamlit/web/server/oidc_mixin.py +3 -1
- streamlit/web/server/routes.py +2 -2
- streamlit/web/server/server.py +9 -0
- streamlit/web/server/server_util.py +3 -1
- streamlit/web/server/upload_file_request_handler.py +3 -1
- {streamlit-1.50.0.dist-info → streamlit-1.51.0.dist-info}/METADATA +4 -5
- {streamlit-1.50.0.dist-info → streamlit-1.51.0.dist-info}/RECORD +222 -194
- streamlit/static/static/css/index.CHEnSPGk.css +0 -1
- streamlit/static/static/js/Hooks.BRba_Own.js +0 -1
- streamlit/static/static/js/InputInstructions.xnSDuYeQ.js +0 -1
- streamlit/static/static/js/index.Baqa90pe.js +0 -2
- streamlit/static/static/js/index.Bm3VbPB5.js +0 -1
- streamlit/static/static/js/index.CFMf5_ez.js +0 -197
- streamlit/static/static/js/index.Cj7DSzVR.js +0 -73
- streamlit/static/static/js/index.DH71Ezyj.js +0 -1
- streamlit/static/static/js/index.DW0Grddz.js +0 -1
- streamlit/static/static/media/MaterialSymbols-Rounded.DeCZgS-4.woff2 +0 -0
- {streamlit-1.50.0.data → streamlit-1.51.0.data}/scripts/streamlit.cmd +0 -0
- {streamlit-1.50.0.dist-info → streamlit-1.51.0.dist-info}/WHEEL +0 -0
- {streamlit-1.50.0.dist-info → streamlit-1.51.0.dist-info}/entry_points.txt +0 -0
- {streamlit-1.50.0.dist-info → streamlit-1.51.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
|
18
|
+
|
|
19
|
+
from typing_extensions import Self
|
|
20
|
+
|
|
21
|
+
from streamlit.errors import StreamlitAPIException
|
|
22
|
+
from streamlit.logger import get_logger
|
|
23
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from streamlit.runtime.state import SessionState
|
|
27
|
+
from streamlit.runtime.state.common import WidgetValuePresenter
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_LOGGER = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _TriggerPayload(TypedDict, total=False):
|
|
34
|
+
event: str
|
|
35
|
+
value: object
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def make_bidi_component_presenter(
|
|
39
|
+
aggregator_id: str,
|
|
40
|
+
component_id: str | None = None,
|
|
41
|
+
allowed_state_keys: set[str] | None = None,
|
|
42
|
+
) -> WidgetValuePresenter:
|
|
43
|
+
"""Return a presenter that merges trigger events into CCv2 state.
|
|
44
|
+
|
|
45
|
+
This function returns a callable that takes a component's persistent state
|
|
46
|
+
value and the current `SessionState` instance, and returns the user-visible
|
|
47
|
+
value that should appear in `st.session_state`.
|
|
48
|
+
|
|
49
|
+
The presenter is side-effect-free and does not mutate stored state or
|
|
50
|
+
callback behavior. It is intended to be attached to the persistent state
|
|
51
|
+
widget via the generic `presenter` hook.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
aggregator_id
|
|
56
|
+
The ID of the trigger aggregator widget that holds the event payloads.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
WidgetValuePresenter
|
|
61
|
+
A callable that merges the trigger event values into the component's
|
|
62
|
+
base state for presentation in `st.session_state`.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def _present(base_value: object, session_state: SessionState) -> object:
|
|
67
|
+
def _check_modification(k: str) -> None:
|
|
68
|
+
ctx = get_script_run_ctx()
|
|
69
|
+
if ctx is not None and component_id is not None:
|
|
70
|
+
user_key = session_state._key_id_mapper.get_key_from_id(component_id)
|
|
71
|
+
if (
|
|
72
|
+
component_id in ctx.widget_ids_this_run
|
|
73
|
+
or user_key in ctx.form_ids_this_run
|
|
74
|
+
):
|
|
75
|
+
raise StreamlitAPIException(
|
|
76
|
+
f"`st.session_state.{user_key}.{k}` cannot be modified after the component"
|
|
77
|
+
f" with key `{user_key}` is instantiated."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Base state must be a flat mapping; otherwise, present as-is.
|
|
81
|
+
base_map: dict[str, object] | None = None
|
|
82
|
+
if isinstance(base_value, dict):
|
|
83
|
+
base_map = cast("dict[str, object]", base_value)
|
|
84
|
+
|
|
85
|
+
if base_map is not None:
|
|
86
|
+
# Read the trigger aggregator payloads if present
|
|
87
|
+
try:
|
|
88
|
+
agg_meta = session_state._new_widget_state.widget_metadata.get(
|
|
89
|
+
aggregator_id
|
|
90
|
+
)
|
|
91
|
+
if agg_meta is None or agg_meta.value_type != "json_trigger_value":
|
|
92
|
+
return base_value
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
agg_payloads_obj = session_state._new_widget_state[aggregator_id]
|
|
96
|
+
except KeyError:
|
|
97
|
+
agg_payloads_obj = None
|
|
98
|
+
|
|
99
|
+
payloads_list: list[_TriggerPayload] | None
|
|
100
|
+
if agg_payloads_obj is None:
|
|
101
|
+
payloads_list = None
|
|
102
|
+
elif isinstance(agg_payloads_obj, list):
|
|
103
|
+
# Filter and cast to the expected payload type shape
|
|
104
|
+
payloads_list = [
|
|
105
|
+
cast("_TriggerPayload", p)
|
|
106
|
+
for p in agg_payloads_obj
|
|
107
|
+
if isinstance(p, dict)
|
|
108
|
+
]
|
|
109
|
+
elif isinstance(agg_payloads_obj, dict):
|
|
110
|
+
payloads_list = [cast("_TriggerPayload", agg_payloads_obj)]
|
|
111
|
+
else:
|
|
112
|
+
payloads_list = None
|
|
113
|
+
|
|
114
|
+
event_to_val: dict[str, object] = {}
|
|
115
|
+
if payloads_list is not None:
|
|
116
|
+
for payload in payloads_list:
|
|
117
|
+
ev = payload.get("event")
|
|
118
|
+
if isinstance(ev, str):
|
|
119
|
+
event_to_val[ev] = payload.get("value")
|
|
120
|
+
|
|
121
|
+
# Merge triggers into a flat view: triggers first, then base
|
|
122
|
+
flat: dict[str, object] = dict(event_to_val)
|
|
123
|
+
flat.update(base_map)
|
|
124
|
+
|
|
125
|
+
# Return a write-through dict that updates the underlying
|
|
126
|
+
# component state when users assign nested keys via
|
|
127
|
+
# st.session_state[component_user_key][name] = value. Using a
|
|
128
|
+
# dict subclass ensures pretty-printing and JSON serialization
|
|
129
|
+
# behave as expected for st.write and logs.
|
|
130
|
+
class _WriteThrough(dict[str, object]):
|
|
131
|
+
def __init__(self, data: dict[str, object]) -> None:
|
|
132
|
+
super().__init__(data)
|
|
133
|
+
|
|
134
|
+
def __getattr__(self, name: str) -> object:
|
|
135
|
+
return self.get(name)
|
|
136
|
+
|
|
137
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
138
|
+
if name.startswith(("__", "_")):
|
|
139
|
+
return super().__setattr__(name, value)
|
|
140
|
+
self[name] = value
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
|
|
144
|
+
# This object is a proxy to the real state. Don't copy it.
|
|
145
|
+
memo[id(self)] = self
|
|
146
|
+
return self
|
|
147
|
+
|
|
148
|
+
def __setitem__(self, k: str, v: object) -> None:
|
|
149
|
+
_check_modification(k)
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
allowed_state_keys is not None
|
|
153
|
+
and k not in allowed_state_keys
|
|
154
|
+
):
|
|
155
|
+
# Silently ignore invalid keys to match permissive session_state semantics
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Update the underlying stored base state and this dict
|
|
159
|
+
super().__setitem__(k, v)
|
|
160
|
+
try:
|
|
161
|
+
# Store back to session state's widget store as a flat mapping
|
|
162
|
+
ss = session_state
|
|
163
|
+
# Directly set the value in the new widget state store
|
|
164
|
+
if component_id is not None:
|
|
165
|
+
ss._new_widget_state.set_from_value(
|
|
166
|
+
component_id, dict(self)
|
|
167
|
+
)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
_LOGGER.debug("Failed to persist CCv2 state update: %s", e)
|
|
170
|
+
|
|
171
|
+
def __delitem__(self, k: str) -> None:
|
|
172
|
+
_check_modification(k)
|
|
173
|
+
|
|
174
|
+
super().__delitem__(k)
|
|
175
|
+
try:
|
|
176
|
+
ss = session_state
|
|
177
|
+
if component_id is not None:
|
|
178
|
+
ss._new_widget_state.set_from_value(
|
|
179
|
+
component_id, dict(self)
|
|
180
|
+
)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
_LOGGER.debug(
|
|
183
|
+
"Failed to persist CCv2 state deletion: %s", e
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return _WriteThrough(flat)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
# On any error, fall back to the base value
|
|
189
|
+
_LOGGER.debug(
|
|
190
|
+
"Failed to merge trigger events into component state: %s",
|
|
191
|
+
e,
|
|
192
|
+
exc_info=e,
|
|
193
|
+
)
|
|
194
|
+
return base_value
|
|
195
|
+
|
|
196
|
+
return base_value
|
|
197
|
+
|
|
198
|
+
return _present
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Shared typing utilities for the `st.components.v2` API.
|
|
16
|
+
|
|
17
|
+
This module exposes common, user-facing argument types and callable
|
|
18
|
+
signatures used by the bidirectional component API. Import these types to
|
|
19
|
+
annotate code that constructs kwargs dictionaries for components, or when
|
|
20
|
+
authoring wrappers/utilities around `st.components.v2.component`.
|
|
21
|
+
|
|
22
|
+
The goal is to keep the public argument surface documented in one place and
|
|
23
|
+
reusable across both the user-facing factory in `components/v2/__init__.py`
|
|
24
|
+
and the internal implementation in `components/v2/bidi_component/main.py`.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from streamlit.components.v2.bidi_component.state import BidiComponentResult
|
|
33
|
+
from streamlit.elements.lib.layout_utils import Height, Width
|
|
34
|
+
from streamlit.runtime.state.common import WidgetCallback
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Individual argument type aliases to make reuse ergonomic across modules.
|
|
38
|
+
BidiComponentKey = str | None
|
|
39
|
+
BidiComponentData = Any | None
|
|
40
|
+
BidiComponentDefaults = dict[str, Any] | None
|
|
41
|
+
ComponentIsolateStyles = bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BidiComponentCallable(Protocol):
|
|
45
|
+
'''Signature of the mounting command returned by ``st.components.v2.component``.
|
|
46
|
+
|
|
47
|
+
This callable mounts a bidirectional component in a Streamlit app and
|
|
48
|
+
returns a ``BidiComponentResult`` object that exposes the component's
|
|
49
|
+
state and trigger values.
|
|
50
|
+
|
|
51
|
+
For published components, this callable is often wrapped in a user-friendly
|
|
52
|
+
command with typed parameters and declared defaults.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
key : str or None
|
|
57
|
+
An optional string to use as the unique key for the
|
|
58
|
+
component instance. If this is omitted, an internal key is generated
|
|
59
|
+
for the component instance based on its mounting parameters. No two
|
|
60
|
+
Streamlit elements may have the same key.
|
|
61
|
+
|
|
62
|
+
When a key is defined, the component's state is available in Session
|
|
63
|
+
State via the key.
|
|
64
|
+
|
|
65
|
+
.. note::
|
|
66
|
+
If you want to access this key in your component's frontend, you
|
|
67
|
+
must pass it explicitly within the ``data`` parameter. The ``key``
|
|
68
|
+
parameter in ``BidiComponentCallable`` is not the same as the
|
|
69
|
+
``key`` property in ``ComponentArgs`` in the component's frontend
|
|
70
|
+
code.
|
|
71
|
+
|
|
72
|
+
The frontend key is automatically generated to be unique among all
|
|
73
|
+
instances of all components and to avoid collisions with classes
|
|
74
|
+
and IDs in the app's DOM.
|
|
75
|
+
|
|
76
|
+
data : Any or None
|
|
77
|
+
Data to pass to the component. This can be one of the following:
|
|
78
|
+
|
|
79
|
+
- A JSON-serializable object, like ``Dict[str, str | int]`` or
|
|
80
|
+
``List[str]``.
|
|
81
|
+
- An Arrow-serializable object, like ``pandas.DataFrame``.
|
|
82
|
+
- Raw bytes.
|
|
83
|
+
- A dictionary of JSON-serializable and Arrow-serializable objects.
|
|
84
|
+
The dictionary's keys must be Python primitives.
|
|
85
|
+
|
|
86
|
+
Because this data is sent to the frontend, it must be serializable by
|
|
87
|
+
one of the supported serialization methods (JSON, Arrow, or raw bytes).
|
|
88
|
+
You can't pass arbitrary Python objects. Arrow-serialization is only
|
|
89
|
+
supported at the top level of the ``data`` parameter or one level deep
|
|
90
|
+
in a dictionary. Raw bytes are only supported at the top level.
|
|
91
|
+
|
|
92
|
+
default : dict[str, Any] or None
|
|
93
|
+
Default state values for the component. Each key in the dictionary must
|
|
94
|
+
correspond to a valid state attribute with an ``on_<key>_change``
|
|
95
|
+
callback. This callback can be empty, but must be included as a
|
|
96
|
+
parameter when the component is mounted.
|
|
97
|
+
|
|
98
|
+
Trigger values do not support manual defaults. All trigger and state
|
|
99
|
+
values defined by an associated callback are initialized to ``None`` by
|
|
100
|
+
default.
|
|
101
|
+
|
|
102
|
+
width : "stretch", "content", or int
|
|
103
|
+
Width of the component. This can be one of the following:
|
|
104
|
+
|
|
105
|
+
- ``"stretch"`` (default): The component is wrapped in a ``<div>`` with
|
|
106
|
+
CSS style ``width: 100%;``.
|
|
107
|
+
- ``"content"``: The component is wrapped in a ``<div>`` with CSS
|
|
108
|
+
style ``width: fit-content;``.
|
|
109
|
+
- An integer specifying the width in pixels: The component is wrapped
|
|
110
|
+
in a ``<div>`` with the specified pixel width.
|
|
111
|
+
|
|
112
|
+
You are responsible for ensuring the component's inner HTML content is
|
|
113
|
+
responsive to the ``<div>`` wrapper.
|
|
114
|
+
|
|
115
|
+
height : "content", "stretch", or int
|
|
116
|
+
Height of the component. This can be one of the following:
|
|
117
|
+
|
|
118
|
+
- ``"content"`` (default): The component is wrapped in a ``<div>`` with
|
|
119
|
+
CSS style ``height: auto;``.
|
|
120
|
+
- ``"stretch"``: The component is wrapped in a ``<div>`` with CSS
|
|
121
|
+
style ``height: 100%;``.
|
|
122
|
+
- An integer specifying the height in pixels: The component is wrapped
|
|
123
|
+
in a ``<div>`` with the specified pixel height. If the component
|
|
124
|
+
content is larger than the specified height, scrolling is enabled.
|
|
125
|
+
|
|
126
|
+
.. note::
|
|
127
|
+
Use scrolling containers sparingly. If you use scrolling
|
|
128
|
+
containers, avoid heights that exceed 500 pixels. Otherwise,
|
|
129
|
+
the scroll surface of the container might cover the majority of
|
|
130
|
+
the screen on mobile devices, which makes it hard to scroll the
|
|
131
|
+
rest of the app.
|
|
132
|
+
|
|
133
|
+
If you want to disable scrolling for a fixed-height component,
|
|
134
|
+
include an inner ``<div>`` wrapper in your component's HTML to
|
|
135
|
+
control the overflow behavior.
|
|
136
|
+
|
|
137
|
+
You are responsible for ensuring the component's inner HTML content is
|
|
138
|
+
responsive to the ``<div>`` wrapper.
|
|
139
|
+
|
|
140
|
+
isolate_styles : bool
|
|
141
|
+
Whether to sandbox the component styles in a shadow root. If this is
|
|
142
|
+
``True`` (default), the component's HTML is mounted inside a shadow DOM
|
|
143
|
+
and, in your component's JavaScript, ``parentElement`` returns a
|
|
144
|
+
``ShadowRoot``. If this is ``False``, the component's HTML is mounted
|
|
145
|
+
directly into the app's DOM tree, and ``parentElement`` returns a
|
|
146
|
+
regular ``HTMLElement``.
|
|
147
|
+
|
|
148
|
+
**callbacks : Callable or None
|
|
149
|
+
Callbacks with the naming pattern ``on_<key>_change`` for each state and
|
|
150
|
+
trigger key. For example, if your component has a state key of
|
|
151
|
+
``"value"`` and a trigger key of ``"click"``, its callbacks can include
|
|
152
|
+
``on_value_change`` and ``on_click_change``.
|
|
153
|
+
|
|
154
|
+
Only names that follow this pattern are recognized. Custom components
|
|
155
|
+
don't currently support callbacks with arguments.
|
|
156
|
+
|
|
157
|
+
Callbacks are required for any state values defined in the ``default``
|
|
158
|
+
parameter. Otherwise, a callback is optional. To ensure your
|
|
159
|
+
component's result always returns the expected attributes, you can pass
|
|
160
|
+
empty callbacks like ``lambda: None``.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
BidiComponentResult
|
|
165
|
+
Component state object that exposes state and trigger values.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
**Example 1: Create a bidirectional text input component**
|
|
170
|
+
|
|
171
|
+
If you assign a key to a mounted instance of a component, you can feed its
|
|
172
|
+
state back into the component through the ``data`` parameter. This allows
|
|
173
|
+
you to both read and write state values from Session State. The following
|
|
174
|
+
example has a user-friendly wrapper around the mounting command to provide
|
|
175
|
+
typed parameters and a clean end-user API. A couple buttons demonstrate
|
|
176
|
+
programmatic updates to the component's state.
|
|
177
|
+
|
|
178
|
+
.. code-block:: python
|
|
179
|
+
|
|
180
|
+
import streamlit as st
|
|
181
|
+
|
|
182
|
+
HTML = """
|
|
183
|
+
<label style='padding-right: 1em;' for='txt'>Enter text</label>
|
|
184
|
+
<input id='txt' type='text' />
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
JS = """
|
|
188
|
+
export default function(component) {
|
|
189
|
+
const { setStateValue, parentElement, data } = component;
|
|
190
|
+
|
|
191
|
+
const label = parentElement.querySelector('label');
|
|
192
|
+
label.innerText = data.label;
|
|
193
|
+
|
|
194
|
+
const input = parentElement.querySelector('input');
|
|
195
|
+
if (input.value !== data.value) {
|
|
196
|
+
input.value = data.value ?? '';
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
input.onkeydown = (e) => {
|
|
200
|
+
if (e.key === 'Enter') {
|
|
201
|
+
setStateValue('value', e.target.value);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
input.onblur = (e) => {
|
|
206
|
+
setStateValue('value', e.target.value);
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
my_component = st.components.v2.component(
|
|
212
|
+
"my_text_input",
|
|
213
|
+
html=HTML,
|
|
214
|
+
js=JS,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def my_component_wrapper(
|
|
219
|
+
label, *, default="", key=None, on_change=lambda: None
|
|
220
|
+
):
|
|
221
|
+
component_state = st.session_state.get(key, {})
|
|
222
|
+
value = component_state.get("value", default)
|
|
223
|
+
data = {"label": label, "value": value}
|
|
224
|
+
result = my_component(
|
|
225
|
+
data=data,
|
|
226
|
+
default={"value": value},
|
|
227
|
+
key=key,
|
|
228
|
+
on_value_change=on_change,
|
|
229
|
+
)
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
st.title("My custom component")
|
|
234
|
+
|
|
235
|
+
if st.button("Hello World"):
|
|
236
|
+
st.session_state["my_text_input_instance"]["value"] = "Hello World"
|
|
237
|
+
if st.button("Clear text"):
|
|
238
|
+
st.session_state["my_text_input_instance"]["value"] = ""
|
|
239
|
+
result = my_component_wrapper(
|
|
240
|
+
"Enter something",
|
|
241
|
+
default="I love Streamlit!",
|
|
242
|
+
key="my_text_input_instance",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
st.write("Result:", result)
|
|
246
|
+
st.write("Session state:", st.session_state)
|
|
247
|
+
|
|
248
|
+
.. output ::
|
|
249
|
+
https://doc-components-text-input.streamlit.app/
|
|
250
|
+
height: 600px
|
|
251
|
+
|
|
252
|
+
**Example 2: Add Tailwind CSS to a component**
|
|
253
|
+
|
|
254
|
+
You can use the ``isolate_styles`` parameter to disable shadow DOM
|
|
255
|
+
isolation and apply global styles like Tailwind CSS to your component. The
|
|
256
|
+
following example creates a simple button styled with Tailwind CSS. This
|
|
257
|
+
example also demonstrates using different keys to mount multiple instances
|
|
258
|
+
of the same component in one app.
|
|
259
|
+
|
|
260
|
+
.. code-block:: python
|
|
261
|
+
|
|
262
|
+
import streamlit as st
|
|
263
|
+
|
|
264
|
+
with open("tailwind.js", "r") as f:
|
|
265
|
+
TAILWIND_SCRIPT = f.read()
|
|
266
|
+
|
|
267
|
+
HTML = """
|
|
268
|
+
<button class="bg-blue-500 hover:bg-blue-700 text-white py-1 px-3 rounded">
|
|
269
|
+
Click me!
|
|
270
|
+
</button>
|
|
271
|
+
"""
|
|
272
|
+
JS = (
|
|
273
|
+
TAILWIND_SCRIPT
|
|
274
|
+
+ """
|
|
275
|
+
export default function(component) {
|
|
276
|
+
const { setTriggerValue, parentElement } = component;
|
|
277
|
+
const button = parentElement.querySelector('button');
|
|
278
|
+
button.onclick = () => {
|
|
279
|
+
setTriggerValue('clicked', true);
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
"""
|
|
283
|
+
)
|
|
284
|
+
my_component = st.components.v2.component(
|
|
285
|
+
"my_tailwind_button",
|
|
286
|
+
html=HTML,
|
|
287
|
+
js=JS,
|
|
288
|
+
)
|
|
289
|
+
result_1 = my_component(
|
|
290
|
+
isolate_styles=False, on_clicked_change=lambda: None, key="one"
|
|
291
|
+
)
|
|
292
|
+
result_1
|
|
293
|
+
|
|
294
|
+
result_2 = my_component(
|
|
295
|
+
isolate_styles=False, on_clicked_change=lambda: None, key="two"
|
|
296
|
+
)
|
|
297
|
+
result_2
|
|
298
|
+
|
|
299
|
+
.. output ::
|
|
300
|
+
https://doc-components-tailwind-button.streamlit.app/
|
|
301
|
+
height: 350px
|
|
302
|
+
|
|
303
|
+
'''
|
|
304
|
+
|
|
305
|
+
def __call__(
|
|
306
|
+
self,
|
|
307
|
+
*,
|
|
308
|
+
key: BidiComponentKey = None,
|
|
309
|
+
data: BidiComponentData = None,
|
|
310
|
+
default: BidiComponentDefaults = None,
|
|
311
|
+
width: Width = "stretch",
|
|
312
|
+
height: Height = "content",
|
|
313
|
+
isolate_styles: ComponentIsolateStyles = True,
|
|
314
|
+
**on_callbacks: WidgetCallback | None,
|
|
315
|
+
) -> BidiComponentResult: ...
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
__all__ = [
|
|
319
|
+
"BidiComponentCallable",
|
|
320
|
+
"BidiComponentData",
|
|
321
|
+
"BidiComponentDefaults",
|
|
322
|
+
"BidiComponentKey",
|
|
323
|
+
"ComponentIsolateStyles",
|
|
324
|
+
]
|