streamlit-nightly 1.53.2.dev20260125__py3-none-any.whl → 1.53.2.dev20260128__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.
Files changed (125) hide show
  1. streamlit/commands/logo.py +81 -25
  2. streamlit/config.py +11 -0
  3. streamlit/deprecation_util.py +19 -1
  4. streamlit/elements/arrow.py +2 -1
  5. streamlit/elements/lib/built_in_chart_utils.py +2 -2
  6. streamlit/elements/lib/options_selector_utils.py +72 -22
  7. streamlit/elements/widgets/select_slider.py +123 -37
  8. streamlit/hello/plotting_demo.py +19 -12
  9. streamlit/proto/Logo_pb2.py +5 -3
  10. streamlit/proto/Logo_pb2.pyi +25 -1
  11. streamlit/proto/NewSession_pb2.py +24 -22
  12. streamlit/proto/NewSession_pb2.pyi +23 -1
  13. streamlit/proto/Slider_pb2.py +6 -6
  14. streamlit/proto/Slider_pb2.pyi +9 -1
  15. streamlit/runtime/app_session.py +19 -0
  16. streamlit/runtime/scriptrunner/script_runner.py +17 -0
  17. streamlit/runtime/scriptrunner_utils/script_run_context.py +13 -10
  18. streamlit/runtime/state/__init__.py +7 -1
  19. streamlit/runtime/state/common.py +13 -0
  20. streamlit/runtime/state/query_params.py +494 -6
  21. streamlit/runtime/state/session_state.py +178 -3
  22. streamlit/runtime/state/widgets.py +26 -1
  23. streamlit/static/index.html +1 -1
  24. streamlit/static/manifest.json +299 -299
  25. streamlit/static/static/js/{ErrorOutline.esm.CIFYUdwC.js → ErrorOutline.esm.D71F8ziR.js} +1 -1
  26. streamlit/static/static/js/{FileDownload.esm.DWVTnTHm.js → FileDownload.esm.yTkppsJy.js} +1 -1
  27. streamlit/static/static/js/{FileHelper.BPYQIPd1.js → FileHelper.hUOqtbwa.js} +1 -1
  28. streamlit/static/static/js/{FormClearHelper.CypmvhYZ.js → FormClearHelper.DN8D_YXO.js} +1 -1
  29. streamlit/static/static/js/{InputInstructions.Bi62hDTQ.js → InputInstructions.DbssY6d4.js} +1 -1
  30. streamlit/static/static/js/{Particles.yebG0VuV.js → Particles.BznyVdfo.js} +1 -1
  31. streamlit/static/static/js/{ProgressBar.Dy9CI6w4.js → ProgressBar.C5uBOtcx.js} +1 -1
  32. streamlit/static/static/js/{StreamlitSyntaxHighlighter.Btk92CPv.js → StreamlitSyntaxHighlighter.Nf1895x-.js} +1 -1
  33. streamlit/static/static/js/{TableChart.esm.DBeVaFNt.js → TableChart.esm.DHKzVs3a.js} +1 -1
  34. streamlit/static/static/js/{Toolbar.DC2Tp-qb.js → Toolbar.CQsWYXer.js} +1 -1
  35. streamlit/static/static/js/{WidgetLabelHelpIconInline.3DnEd9BK.js → WidgetLabelHelpIconInline.6xCU76OE.js} +1 -1
  36. streamlit/static/static/js/{base-input.7Sj6pVk0.js → base-input.Cs-E6S71.js} +1 -1
  37. streamlit/static/static/js/{checkbox.CcUx3XuQ.js → checkbox.OTGupu18.js} +1 -1
  38. streamlit/static/static/js/{createDownloadLinkElement.DZuwkCqy.js → createDownloadLinkElement.DnBEQQbK.js} +1 -1
  39. streamlit/static/static/js/{data-grid-overlay-editor.Dw-AewlN.js → data-grid-overlay-editor.COiiMi5r.js} +1 -1
  40. streamlit/static/static/js/{downloader.Bsx5M2Du.js → downloader.K0GUNeuj.js} +1 -1
  41. streamlit/static/static/js/embed.o8HvK3mH.js +193 -0
  42. streamlit/static/static/js/{es6.BpAqZaR_.js → es6.BHy5pqTP.js} +2 -2
  43. streamlit/static/static/js/{formatNumber.DjehVPVS.js → formatNumber.BK7h0k2z.js} +1 -1
  44. streamlit/static/static/js/{iconPosition.D02OPE-d.js → iconPosition.2YynQUxu.js} +1 -1
  45. streamlit/static/static/js/{iframeResizer.contentWindow.xtstqPd7.js → iframeResizer.contentWindow.D5h3hQuU.js} +1 -1
  46. streamlit/static/static/js/{index.5H98WqjT.js → index.5zqfJ-in.js} +1 -1
  47. streamlit/static/static/js/{index.B5tD5YeV.js → index.6c-qDsD7.js} +1 -1
  48. streamlit/static/static/js/{index.CA0RmxJF.js → index.8MlRyIxN.js} +1 -1
  49. streamlit/static/static/js/{index.DSSapl3Q.js → index.BIqcOZ_u.js} +1 -1
  50. streamlit/static/static/js/{index.DJjSqPAx.js → index.BPdmXoYW.js} +1 -1
  51. streamlit/static/static/js/{index.iXzAofuY.js → index.BZ-GJVxB.js} +2 -2
  52. streamlit/static/static/js/{index.CKUBdVQ9.js → index.BfMPq234.js} +1 -1
  53. streamlit/static/static/js/{index.B8-HOwf1.js → index.Bfo1cXfC.js} +1 -1
  54. streamlit/static/static/js/{index.-faJDV20.js → index.Bgf49D1Z.js} +1 -1
  55. streamlit/static/static/js/{index.CgARjn28.js → index.Bqmx23jK.js} +1 -1
  56. streamlit/static/static/js/{index.D6Z9hKJY.js → index.BtRWcqZV.js} +1 -1
  57. streamlit/static/static/js/{index.ZIA43eTF.js → index.BtuskCwg.js} +1 -1
  58. streamlit/static/static/js/{index.BV6XgCij.js → index.BzTVI_BY.js} +1 -1
  59. streamlit/static/static/js/{index.DDu_qTm0.js → index.C2EoeVjP.js} +1 -1
  60. streamlit/static/static/js/{index.8FPw0_gD.js → index.C65jHNhe.js} +1 -1
  61. streamlit/static/static/js/{index.D6J2UPzF.js → index.C6wyTXhz.js} +1 -1
  62. streamlit/static/static/js/{index.CGbvkEtg.js → index.C7wst9Tm.js} +1 -1
  63. streamlit/static/static/js/{index.BIcJe97b.js → index.COh5V_89.js} +1 -1
  64. streamlit/static/static/js/index.CSPY26T2.js +1 -0
  65. streamlit/static/static/js/{index.CEwnDCn9.js → index.CUkhn-vu.js} +1 -1
  66. streamlit/static/static/js/{index.DO2T-QzF.js → index.CX0KdFyR.js} +1 -1
  67. streamlit/static/static/js/{index.BDlI2pRp.js → index.CYhhEdja.js} +1 -1
  68. streamlit/static/static/js/{index.DgLRJfs3.js → index.CZf7Go1Z.js} +1 -1
  69. streamlit/static/static/js/{index.DZv5AoR1.js → index.Cb03y5I8.js} +1 -1
  70. streamlit/static/static/js/{index.BVhVdVeE.js → index.CdsyTabv.js} +1 -1
  71. streamlit/static/static/js/{index.JL0uGAeJ.js → index.CgVv04GM.js} +1 -1
  72. streamlit/static/static/js/index.CjRU8O1O.js +2 -0
  73. streamlit/static/static/js/{index.BqfJJr3c.js → index.CwtpGPHA.js} +1 -1
  74. streamlit/static/static/js/{index.iF5zYERg.js → index.CxWzt6oi.js} +1 -1
  75. streamlit/static/static/js/{index.m3dn5Bai.js → index.DBPWUJsj.js} +5 -5
  76. streamlit/static/static/js/{index.D9RL5sRp.js → index.DJfMW0Gy.js} +1 -1
  77. streamlit/static/static/js/{index.BOkpEbJS.js → index.DLUSo6de.js} +1 -1
  78. streamlit/static/static/js/{index.S-mjkUeF.js → index.DL_yE83J.js} +1 -1
  79. streamlit/static/static/js/{index.BK9S5qug.js → index.DVRCyxMp.js} +1 -1
  80. streamlit/static/static/js/{index.D_TIyPF4.js → index.Dc5-tFdw.js} +1 -1
  81. streamlit/static/static/js/index.DcngUOyD.js +2 -0
  82. streamlit/static/static/js/{index.B9gbSNsw.js → index.Dh3PJIlq.js} +1 -1
  83. streamlit/static/static/js/{index.BvZbnSMC.js → index.DlgcEr0f.js} +1 -1
  84. streamlit/static/static/js/{index.Bo1ztye0.js → index.DxGXuhh6.js} +1 -1
  85. streamlit/static/static/js/{index.x1B588Xu.js → index.DxfYCrPp.js} +1 -1
  86. streamlit/static/static/js/{index.CyDHwK5P.js → index.HmRK3HyC.js} +1 -1
  87. streamlit/static/static/js/{index.DdxofXV8.js → index.TjMWsKSH.js} +3 -3
  88. streamlit/static/static/js/{index.Bri1T2TS.js → index.VwDKazgt.js} +1 -1
  89. streamlit/static/static/js/{index.BB_iwaVr.js → index.aCorc3Yt.js} +34 -34
  90. streamlit/static/static/js/{index.CdRwiHPm.js → index.cfuZ69LI.js} +1 -1
  91. streamlit/static/static/js/{index.C9v49R-a.js → index.hlAfdSqC.js} +1 -1
  92. streamlit/static/static/js/{index.BGTMh3Uu.js → index.iUV9rb8C.js} +1 -1
  93. streamlit/static/static/js/{index.XFMDBL5n.js → index.q0ceUXt6.js} +1 -1
  94. streamlit/static/static/js/{input.VYKyGuhi.js → input.CXGIJ7D6.js} +1 -1
  95. streamlit/static/static/js/{main.u5Bb3MY7.js → main.CCVkbuxC.js} +1 -1
  96. streamlit/static/static/js/{memory.BOMt4yAV.js → memory.CNbnYs2A.js} +1 -1
  97. streamlit/static/static/js/{number-overlay-editor.CihlAHgl.js → number-overlay-editor.CvI6wkld.js} +1 -1
  98. streamlit/static/static/js/{pandasStylerUtils.BuqSgXpk.js → pandasStylerUtils.CFSReOTm.js} +1 -1
  99. streamlit/static/static/js/{sandbox.COGR4pqz.js → sandbox.Bld0L3us.js} +1 -1
  100. streamlit/static/static/js/{styled-components.BEf3c4IJ.js → styled-components.BoUHK6TA.js} +1 -1
  101. streamlit/static/static/js/{throttle.Bl-XsA9N.js → throttle.ByDFm7WV.js} +1 -1
  102. streamlit/static/static/js/{timepicker.B-HgBYlK.js → timepicker.CN6CUZEL.js} +1 -1
  103. streamlit/static/static/js/{toConsumableArray.BrQebwtE.js → toConsumableArray.DwMycSpg.js} +1 -1
  104. streamlit/static/static/js/uniqueId.DcCWa2cf.js +1 -0
  105. streamlit/static/static/js/{useBasicWidgetState.8WwISl9r.js → useBasicWidgetState.Bg0ZMUt5.js} +1 -1
  106. streamlit/static/static/js/{useIntlLocale.D37LWdCR.js → useIntlLocale.DgBUDcPA.js} +1 -1
  107. streamlit/static/static/js/{useTextInputAutoExpand.Bb_KqJvq.js → useTextInputAutoExpand.DDBezxks.js} +1 -1
  108. streamlit/static/static/js/{useUpdateUiValue.D1BLS5t7.js → useUpdateUiValue.Df1h6fXC.js} +1 -1
  109. streamlit/static/static/js/{useWaveformController.Ce0-qTws.js → useWaveformController.DbWw5MEk.js} +1 -1
  110. streamlit/static/static/js/{withCalculatedWidth.BX2K3UVv.js → withCalculatedWidth.YaK0HIIP.js} +1 -1
  111. streamlit/static/static/js/{withFullScreenWrapper.CqfGs8T2.js → withFullScreenWrapper.CcWCKoY8.js} +1 -1
  112. streamlit/testing/v1/element_tree.py +23 -8
  113. streamlit/web/bootstrap.py +5 -2
  114. streamlit/web/server/server_util.py +22 -0
  115. {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/METADATA +1 -1
  116. {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/RECORD +120 -120
  117. streamlit/static/static/js/embed.C7by6AoE.js +0 -195
  118. streamlit/static/static/js/index.Bhy8EBYI.js +0 -2
  119. streamlit/static/static/js/index.C5ehUqNt.js +0 -2
  120. streamlit/static/static/js/index.m4WkwGMu.js +0 -1
  121. streamlit/static/static/js/uniqueId.8R4hbkYl.js +0 -1
  122. {streamlit_nightly-1.53.2.dev20260125.data → streamlit_nightly-1.53.2.dev20260128.data}/scripts/streamlit.cmd +0 -0
  123. {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/WHEEL +0 -0
  124. {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/entry_points.txt +0 -0
  125. {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/top_level.txt +0 -0
@@ -30,6 +30,7 @@ from typing import (
30
30
  from streamlit import config, util
31
31
  from streamlit.delta_generator_singletons import get_dg_singleton_instance
32
32
  from streamlit.errors import StreamlitAPIException, UnserializableSessionStateError
33
+ from streamlit.logger import get_logger
33
34
  from streamlit.proto.WidgetStates_pb2 import WidgetState as WidgetStateProto
34
35
  from streamlit.proto.WidgetStates_pb2 import WidgetStates as WidgetStatesProto
35
36
  from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
@@ -45,7 +46,7 @@ from streamlit.runtime.state.common import (
45
46
  is_keyed_element_id,
46
47
  )
47
48
  from streamlit.runtime.state.presentation import apply_presenter
48
- from streamlit.runtime.state.query_params import QueryParams
49
+ from streamlit.runtime.state.query_params import QueryParams, parse_url_param
49
50
  from streamlit.runtime.stats import (
50
51
  CACHE_MEMORY_FAMILY,
51
52
  CacheStat,
@@ -53,6 +54,8 @@ from streamlit.runtime.stats import (
53
54
  group_cache_stats,
54
55
  )
55
56
 
57
+ _LOGGER: Final = get_logger(__name__)
58
+
56
59
  if TYPE_CHECKING:
57
60
  from streamlit.runtime.session_manager import SessionManager
58
61
 
@@ -868,6 +871,17 @@ class SessionState:
868
871
  )
869
872
  }
870
873
 
874
+ # Remove query param bindings and URL params for stale widgets.
875
+ # For fragment runs, preserve widgets outside the running fragment(s).
876
+ # Note: For MPA page transitions, query param filtering is performed
877
+ # via populate_from_query_string() in script_runner.py before this cleanup,
878
+ # so bindings for non-active pages are already filtered by script hash.
879
+ self.query_params.remove_stale_bindings(
880
+ active_widget_ids,
881
+ ctx.fragment_ids_this_run,
882
+ self._new_widget_state.widget_metadata,
883
+ )
884
+
871
885
  def _get_widget_metadata(self, widget_id: str) -> WidgetMetadata[Any] | None:
872
886
  """Return the metadata for a widget id from the current widget state."""
873
887
  return self._new_widget_state.widget_metadata.get(widget_id)
@@ -910,9 +924,20 @@ class SessionState:
910
924
  # If the widget has a user_key, update its user_key:widget_id mapping
911
925
  self._set_key_widget_mapping(widget_id, user_key)
912
926
 
913
- if widget_id not in self and (user_key is None or user_key not in self):
927
+ # Handle query param binding
928
+ url_value_seeded = False
929
+ if metadata.bind == "query-params" and user_key is not None:
930
+ url_value_seeded = self._handle_query_param_binding(
931
+ metadata, user_key, widget_id
932
+ )
933
+
934
+ if (
935
+ widget_id not in self
936
+ and (user_key is None or user_key not in self)
937
+ and not url_value_seeded
938
+ ):
914
939
  # This is the first time the widget is registered, so we save its
915
- # value in widget state.
940
+ # value in widget state (unless we already seeded from URL).
916
941
  deserializer = metadata.deserializer
917
942
  initial_widget_value = deepcopy(deserializer(None))
918
943
  self._new_widget_state.set_from_value(widget_id, initial_widget_value)
@@ -931,6 +956,156 @@ class SessionState:
931
956
 
932
957
  return RegisterWidgetResult(widget_value, widget_value_changed)
933
958
 
959
+ def _handle_query_param_binding(
960
+ self, metadata: WidgetMetadata[T], user_key: str, widget_id: str
961
+ ) -> bool:
962
+ """Handle query param binding for a widget.
963
+
964
+ Registers the binding, then attempts to seed the widget's value from URL
965
+ based on priority rules:
966
+
967
+ - On initial load, URL wins (enables shareable URLs)
968
+ - On subsequent reruns, session_state values win
969
+ - User interaction (frontend value) always wins
970
+
971
+ Returns True if the widget's value was seeded from URL, False otherwise.
972
+ """
973
+ # Register the widget binding
974
+ ctx = get_script_run_ctx()
975
+ script_hash = ctx.active_script_hash if ctx is not None else ""
976
+ self.query_params.bind_widget(
977
+ param_key=user_key,
978
+ widget_id=widget_id,
979
+ value_type=metadata.value_type,
980
+ script_hash=script_hash,
981
+ )
982
+
983
+ # Check priority rules - skip seeding if user/code has already set a value
984
+ if widget_id in self._new_widget_state: # User interacted with widget
985
+ return False
986
+ is_initial_load = widget_id not in self._old_state
987
+ if not is_initial_load and user_key in self._new_session_state:
988
+ return False # Code set value after first run
989
+
990
+ url_value = self.query_params.get_initial_value(user_key)
991
+ if url_value is None:
992
+ return False
993
+
994
+ return self._seed_widget_from_url(metadata, user_key, widget_id, url_value)
995
+
996
+ def _seed_widget_from_url(
997
+ self,
998
+ metadata: WidgetMetadata[T],
999
+ user_key: str,
1000
+ widget_id: str,
1001
+ url_value: str | list[str],
1002
+ ) -> bool:
1003
+ """Parse URL value, seed widget state, and auto-correct URL if needed.
1004
+
1005
+ This method:
1006
+ 1. Parses the raw URL string using the widget's value_type
1007
+ 2. Deserializes to the widget's native value format
1008
+ 3. Handles invalid values (clears URL param, returns False)
1009
+ 4. Stores valid values in both widget state and session state
1010
+ 5. Auto-corrects the URL if the value was clamped/filtered
1011
+
1012
+ Returns True if seeding succeeded, False if the URL value was invalid.
1013
+ """
1014
+ try:
1015
+ parsed_value = parse_url_param(url_value, metadata.value_type)
1016
+ deserialized_value = metadata.deserializer(parsed_value)
1017
+
1018
+ # Handle case where all URL values were invalid (filtered to empty list)
1019
+ if isinstance(deserialized_value, list) and len(deserialized_value) == 0:
1020
+ url_had_values = (
1021
+ isinstance(parsed_value, list) and len(parsed_value) > 0
1022
+ ) or (isinstance(parsed_value, str) and len(parsed_value) > 0)
1023
+ if url_had_values:
1024
+ self._clear_url_param(user_key)
1025
+ return False
1026
+
1027
+ # Store the value in widget and session state
1028
+ self._new_widget_state.set_from_value(widget_id, deserialized_value)
1029
+ self._new_session_state[user_key] = deserialized_value
1030
+
1031
+ # Auto-correct URL if value was clamped/filtered
1032
+ self._auto_correct_url_if_needed(
1033
+ metadata, user_key, parsed_value, deserialized_value
1034
+ )
1035
+ return True
1036
+
1037
+ except (ValueError, TypeError, IndexError) as e:
1038
+ _LOGGER.debug(
1039
+ "Invalid URL value for bound widget '%s', clearing param: %s",
1040
+ user_key,
1041
+ e,
1042
+ )
1043
+ self._clear_url_param(user_key)
1044
+ return False
1045
+
1046
+ def _clear_url_param(self, user_key: str) -> None:
1047
+ """Clear an invalid URL parameter and notify frontend."""
1048
+ self.query_params.remove_param(user_key)
1049
+
1050
+ def _auto_correct_url_if_needed(
1051
+ self,
1052
+ metadata: WidgetMetadata[T],
1053
+ user_key: str,
1054
+ parsed_value: Any,
1055
+ deserialized_value: Any,
1056
+ ) -> None:
1057
+ """Auto-correct URL if the value was clamped or filtered.
1058
+
1059
+ For selection widgets using human-readable strings in URLs, we preserve
1060
+ the original strings unless values were actually filtered out.
1061
+ """
1062
+ serialized_value = metadata.serializer(deserialized_value)
1063
+ if serialized_value == parsed_value:
1064
+ return # No correction needed
1065
+
1066
+ # TODO(query-params): Remove this formatted_options handling once all selection
1067
+ # widgets use string-based wire formats (string_value/string_array_value).
1068
+ # For index-based widgets, don't auto-correct valid strings to indices -
1069
+ # only correct if values were actually filtered.
1070
+ string_option_types = ("int_value", "int_array_value", "double_array_value")
1071
+ use_formatted_options = False
1072
+
1073
+ if metadata.value_type in string_option_types:
1074
+ # Check if parsed value contained strings
1075
+ if isinstance(parsed_value, list):
1076
+ parsed_has_strings = any(isinstance(v, str) for v in parsed_value)
1077
+ parsed_len = len(parsed_value)
1078
+ else:
1079
+ parsed_has_strings = isinstance(parsed_value, str)
1080
+ parsed_len = 1
1081
+
1082
+ if parsed_has_strings:
1083
+ serialized_len = (
1084
+ len(serialized_value) if isinstance(serialized_value, list) else 1
1085
+ )
1086
+ if serialized_len == parsed_len:
1087
+ return # No filtering, keep original strings in URL
1088
+ use_formatted_options = bool(metadata.formatted_options)
1089
+
1090
+ # Build corrected value, converting indices to formatted strings if needed
1091
+ corrected_value = serialized_value
1092
+ if use_formatted_options and metadata.formatted_options:
1093
+ fmt_opts = metadata.formatted_options
1094
+ if isinstance(serialized_value, list):
1095
+ corrected_value = [
1096
+ fmt_opts[idx]
1097
+ for idx in serialized_value
1098
+ if isinstance(idx, int) and 0 <= idx < len(fmt_opts)
1099
+ ]
1100
+ elif isinstance(serialized_value, int) and 0 <= serialized_value < len(
1101
+ fmt_opts
1102
+ ):
1103
+ corrected_value = fmt_opts[serialized_value]
1104
+
1105
+ self.query_params._set_corrected_value(
1106
+ user_key, corrected_value, metadata.value_type
1107
+ )
1108
+
934
1109
  def __contains__(self, key: str) -> bool:
935
1110
  try:
936
1111
  self[key]
@@ -18,6 +18,7 @@ from typing import TYPE_CHECKING
18
18
 
19
19
  from streamlit.errors import StreamlitAPIException
20
20
  from streamlit.runtime.state.common import (
21
+ BindOption,
21
22
  RegisterWidgetResult,
22
23
  T,
23
24
  ValueFieldName,
@@ -47,6 +48,10 @@ def register_widget(
47
48
  kwargs: WidgetKwargs | None = None,
48
49
  value_type: ValueFieldName,
49
50
  presenter: WidgetValuePresenter | None = None,
51
+ bind: BindOption = None,
52
+ # TODO(query-params): Remove formatted_options once all selection widgets use
53
+ # string-based wire formats (string_value/string_array_value).
54
+ formatted_options: list[str] | None = None,
50
55
  ) -> RegisterWidgetResult[T]:
51
56
  """Register a widget with Streamlit, and return its current value.
52
57
  NOTE: This function should be called after the proto has been filled.
@@ -81,7 +86,15 @@ def register_widget(
81
86
  presenter : WidgetValuePresenter or None
82
87
  An optional hook that allows a widget to customize how its value should be
83
88
  presented.
84
-
89
+ bind : BindOption
90
+ Optional binding for the widget's value to external state.
91
+ Currently only "query-params" is supported, which binds the widget
92
+ value to a URL query parameter. Requires a user-provided key.
93
+ formatted_options : list[str] or None
94
+ **Temporary** - will be removed once all selection widgets use string-based
95
+ wire formats. Currently used for index-based widgets (pills, segmented_control,
96
+ select_slider) to convert indices back to human-readable option strings
97
+ in URLs when auto-correcting filtered values.
85
98
 
86
99
  Returns
87
100
  -------
@@ -112,6 +125,16 @@ def register_widget(
112
125
  "Cannot provide both `on_change` and `callbacks` to a widget."
113
126
  )
114
127
 
128
+ # Validate that widget with bind="query-params" has a provided key
129
+ if bind == "query-params":
130
+ user_key = user_key_from_element_id(element_id)
131
+ if user_key is None:
132
+ raise StreamlitAPIException(
133
+ "When using bind='query-params', the widget must have a unique 'key' "
134
+ "parameter specified. This 'key' will be used as the name of the "
135
+ "query parameter."
136
+ )
137
+
115
138
  # Create the widget's updated metadata, and register it with session_state.
116
139
  metadata = WidgetMetadata(
117
140
  element_id,
@@ -124,6 +147,8 @@ def register_widget(
124
147
  callback_kwargs=kwargs,
125
148
  fragment_id=ctx.current_fragment_id if ctx else None,
126
149
  presenter=presenter,
150
+ bind=bind,
151
+ formatted_options=formatted_options,
127
152
  )
128
153
  return register_widget_from_metadata(metadata, ctx)
129
154
 
@@ -37,7 +37,7 @@
37
37
  <script>
38
38
  window.prerenderReady = false
39
39
  </script>
40
- <script type="module" crossorigin src="./static/js/index.BB_iwaVr.js"></script>
40
+ <script type="module" crossorigin src="./static/js/index.aCorc3Yt.js"></script>
41
41
  <link rel="stylesheet" crossorigin href="./static/css/index.BUP6fTcR.css">
42
42
  </head>
43
43
  <body>