streamlit 1.47.1__py3-none-any.whl → 1.48.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.
Files changed (189) hide show
  1. streamlit/cli_util.py +1 -1
  2. streamlit/commands/echo.py +2 -2
  3. streamlit/commands/execution_control.py +1 -1
  4. streamlit/commands/page_config.py +16 -16
  5. streamlit/components/v1/component_arrow.py +4 -4
  6. streamlit/config.py +23 -5
  7. streamlit/connections/base_connection.py +2 -2
  8. streamlit/connections/snowflake_connection.py +2 -2
  9. streamlit/connections/sql_connection.py +4 -3
  10. streamlit/dataframe_util.py +1 -1
  11. streamlit/deprecation_util.py +20 -5
  12. streamlit/elements/arrow.py +105 -79
  13. streamlit/elements/deck_gl_json_chart.py +6 -6
  14. streamlit/elements/dialog_decorator.py +72 -17
  15. streamlit/elements/form.py +36 -7
  16. streamlit/elements/graphviz_chart.py +7 -0
  17. streamlit/elements/json.py +3 -3
  18. streamlit/elements/layouts.py +241 -75
  19. streamlit/elements/lib/built_in_chart_utils.py +11 -3
  20. streamlit/elements/lib/dialog.py +43 -0
  21. streamlit/elements/lib/file_uploader_utils.py +21 -2
  22. streamlit/elements/lib/layout_utils.py +88 -2
  23. streamlit/elements/lib/options_selector_utils.py +1 -1
  24. streamlit/elements/lib/utils.py +23 -3
  25. streamlit/elements/map.py +12 -11
  26. streamlit/elements/plotly_chart.py +21 -21
  27. streamlit/elements/pyplot.py +8 -4
  28. streamlit/elements/vega_charts.py +237 -143
  29. streamlit/elements/widgets/audio_input.py +2 -2
  30. streamlit/elements/widgets/button.py +147 -35
  31. streamlit/elements/widgets/button_group.py +9 -8
  32. streamlit/elements/widgets/camera_input.py +2 -2
  33. streamlit/elements/widgets/chat.py +8 -2
  34. streamlit/elements/widgets/checkbox.py +4 -4
  35. streamlit/elements/widgets/color_picker.py +2 -2
  36. streamlit/elements/widgets/data_editor.py +22 -15
  37. streamlit/elements/widgets/file_uploader.py +8 -2
  38. streamlit/elements/widgets/multiselect.py +27 -17
  39. streamlit/elements/widgets/number_input.py +2 -2
  40. streamlit/elements/widgets/radio.py +5 -4
  41. streamlit/elements/widgets/select_slider.py +8 -5
  42. streamlit/elements/widgets/selectbox.py +14 -4
  43. streamlit/elements/widgets/slider.py +59 -33
  44. streamlit/elements/widgets/text_widgets.py +4 -4
  45. streamlit/elements/widgets/time_widgets.py +4 -4
  46. streamlit/elements/write.py +9 -8
  47. streamlit/env_util.py +1 -1
  48. streamlit/errors.py +20 -4
  49. streamlit/file_util.py +1 -1
  50. streamlit/git_util.py +1 -1
  51. streamlit/logger.py +2 -2
  52. streamlit/proto/Block_pb2.py +39 -31
  53. streamlit/proto/Block_pb2.pyi +54 -3
  54. streamlit/proto/Button_pb2.py +4 -2
  55. streamlit/proto/Button_pb2.pyi +1 -0
  56. streamlit/runtime/caching/cache_data_api.py +15 -2
  57. streamlit/runtime/caching/cache_errors.py +1 -1
  58. streamlit/runtime/caching/cache_resource_api.py +24 -2
  59. streamlit/runtime/caching/cache_utils.py +6 -3
  60. streamlit/runtime/caching/hashing.py +6 -9
  61. streamlit/runtime/fragment.py +3 -2
  62. streamlit/runtime/runtime.py +19 -2
  63. streamlit/runtime/scriptrunner/script_runner.py +3 -3
  64. streamlit/runtime/state/common.py +2 -1
  65. streamlit/runtime/state/query_params.py +2 -1
  66. streamlit/runtime/state/session_state.py +1 -1
  67. streamlit/static/index.html +1 -1
  68. streamlit/static/manifest.json +231 -231
  69. streamlit/static/static/css/index.CQt5TjGB.css +1 -0
  70. streamlit/static/static/js/{ErrorOutline.esm.BEZPMjuG.js → ErrorOutline.esm.D_4oFNKB.js} +1 -1
  71. streamlit/static/static/js/{FileDownload.esm.Dy1V9a2E.js → FileDownload.esm.NPgaLlUE.js} +1 -1
  72. streamlit/static/static/js/{FileHelper.D0K06YBq.js → FileHelper.B2t9ikoS.js} +1 -1
  73. streamlit/static/static/js/{FormClearHelper.Cdw5Y7_m.js → FormClearHelper.BLEIUk6L.js} +1 -1
  74. streamlit/static/static/js/{Hooks.C_qx1sSw.js → Hooks.BGm9sd4U.js} +1 -1
  75. streamlit/static/static/js/{InputInstructions.D3IDU-eY.js → InputInstructions.DtUxCBS8.js} +1 -1
  76. streamlit/static/static/js/Particles.BDRPO7r3.js +1 -0
  77. streamlit/static/static/js/ProgressBar.B64DUUqp.js +2 -0
  78. streamlit/static/static/js/Toolbar.B3FquPk5.js +1 -0
  79. streamlit/static/static/js/{base-input.DMlw5p7n.js → base-input.DeBqm5mN.js} +4 -4
  80. streamlit/static/static/js/{checkbox.C7QR6llE.js → checkbox.C0odQfKb.js} +2 -2
  81. streamlit/static/static/js/createDownloadLinkElement.ZaXNnPK4.js +1 -0
  82. streamlit/static/static/js/{createSuper.C5k_2vfB.js → createSuper.DqQ5L3XG.js} +1 -1
  83. streamlit/static/static/js/data-grid-overlay-editor.DbNsQa8Y.js +1 -0
  84. streamlit/static/static/js/{downloader.Nj6v3ioB.js → downloader.Bx1D0jhz.js} +1 -1
  85. streamlit/static/static/js/{es6.CVz13CSz.js → es6.CbPK4m0H.js} +2 -2
  86. streamlit/static/static/js/{iframeResizer.contentWindow.gZ8zjT0K.js → iframeResizer.contentWindow.CfLKrptA.js} +1 -1
  87. streamlit/static/static/js/{index.D3wOJJsg.js → index.0XDwe9RV.js} +12 -12
  88. streamlit/static/static/js/{index.BscWuWHL.js → index.4lI9TuZm.js} +3 -3
  89. streamlit/static/static/js/index.6s0nVIis.js +3855 -0
  90. streamlit/static/static/js/index.9E7bRUBU.js +1 -0
  91. streamlit/static/static/js/index.B9PgeLrZ.js +1 -0
  92. streamlit/static/static/js/index.B9vzGbOt.js +1 -0
  93. streamlit/static/static/js/index.BDrQKMCm.js +1 -0
  94. streamlit/static/static/js/index.BPsoiGgP.js +1 -0
  95. streamlit/static/static/js/index.CJzdLAun.js +1 -0
  96. streamlit/static/static/js/index.CNNlC1NL.js +1 -0
  97. streamlit/static/static/js/index.CO1sClzJ.js +2 -0
  98. streamlit/static/static/js/index.CZnagxXD.js +1 -0
  99. streamlit/static/static/js/{index.MbqsiUV4.js → index.Cb0xSF7V.js} +289 -289
  100. streamlit/static/static/js/index.CgUt3tz_.js +1 -0
  101. streamlit/static/static/js/index.CjImmcsV.js +1 -0
  102. streamlit/static/static/js/index.ClRTsv8m.js +2 -0
  103. streamlit/static/static/js/{index.CTT2YqEU.js → index.CnfWsQzS.js} +1 -1
  104. streamlit/static/static/js/index.CrJ1XD_V.js +1 -0
  105. streamlit/static/static/js/index.CtiKsjSC.js +1 -0
  106. streamlit/static/static/js/index.CwAuytgV.js +1 -0
  107. streamlit/static/static/js/index.D7GB-kly.js +1 -0
  108. streamlit/static/static/js/index.DA5wU0mQ.js +1 -0
  109. streamlit/static/static/js/{index.xfcNJBLM.js → index.DCpyIFTV.js} +1 -1
  110. streamlit/static/static/js/index.DE9wNOje.js +1 -0
  111. streamlit/static/static/js/{index.mihWZKb1.js → index.DHnB-C8A.js} +1 -1
  112. streamlit/static/static/js/{index.BSFzxMXi.js → index.DRTn9zvD.js} +1 -1
  113. streamlit/static/static/js/index.DjMjyJl9.js +7 -0
  114. streamlit/static/static/js/{index.CbQtRkVt.js → index.DvRPFfw6.js} +162 -188
  115. streamlit/static/static/js/{index.B2L574n6.js → index.DwaoC4Zp.js} +3 -3
  116. streamlit/static/static/js/index.F9tSej94.js +1 -0
  117. streamlit/static/static/js/index.HeVbRh9H.js +73 -0
  118. streamlit/static/static/js/index.J2D_m7LY.js +197 -0
  119. streamlit/static/static/js/index.PyIqRRSR.js +1 -0
  120. streamlit/static/static/js/index.dfivzJNz.js +1 -0
  121. streamlit/static/static/js/index.mRztGO69.js +3 -0
  122. streamlit/static/static/js/index.tB1kn_7z.js +1 -0
  123. streamlit/static/static/js/index.wDYef4mQ.js +12 -0
  124. streamlit/static/static/js/{input.D_45B0P-.js → input.BL2buuce.js} +2 -2
  125. streamlit/static/static/js/{memory.BmhrRyO2.js → memory.CUxjUWS7.js} +1 -1
  126. streamlit/static/static/js/{mergeWith.DvOME7eH.js → mergeWith.C1kp1zIi.js} +1 -1
  127. streamlit/static/static/js/{number-overlay-editor.BRNxOzEZ.js → number-overlay-editor.WpheGpmR.js} +1 -1
  128. streamlit/static/static/js/{possibleConstructorReturn.C5GiK_Ob.js → possibleConstructorReturn.DbvQboK3.js} +1 -1
  129. streamlit/static/static/js/{sandbox.B-Q9S7vW.js → sandbox.6lnFVWhX.js} +1 -1
  130. streamlit/static/static/js/{timepicker.DOMbfm_a.js → timepicker.Bg4xAK95.js} +1 -1
  131. streamlit/static/static/js/{toConsumableArray.CbNz0Ciu.js → toConsumableArray.D9x7Ktv4.js} +1 -1
  132. streamlit/static/static/js/{uniqueId.-ygIU7IL.js → uniqueId.Bm8FHN92.js} +1 -1
  133. streamlit/static/static/js/{useBasicWidgetState.Bx3VaRHk.js → useBasicWidgetState.CUSYQZpm.js} +1 -1
  134. streamlit/static/static/js/{useTextInputAutoExpand.BuE9l5TG.js → useTextInputAutoExpand.Bf2egQOG.js} +2 -2
  135. streamlit/static/static/js/useUpdateUiValue.lE5xnYWF.js +1 -0
  136. streamlit/static/static/js/withFullScreenWrapper.CCOXR7N6.js +1 -0
  137. streamlit/temporary_directory.py +5 -3
  138. streamlit/testing/v1/app_test.py +4 -1
  139. streamlit/testing/v1/element_tree.py +4 -5
  140. streamlit/type_util.py +10 -3
  141. streamlit/user_info.py +18 -14
  142. streamlit/util.py +11 -1
  143. streamlit/watcher/local_sources_watcher.py +4 -3
  144. streamlit/web/bootstrap.py +18 -12
  145. streamlit/web/cli.py +2 -1
  146. streamlit/web/server/oauth_authlib_routes.py +59 -3
  147. streamlit/web/server/server.py +72 -21
  148. {streamlit-1.47.1.dist-info → streamlit-1.48.0.dist-info}/METADATA +14 -2
  149. {streamlit-1.47.1.dist-info → streamlit-1.48.0.dist-info}/RECORD +153 -153
  150. streamlit/static/static/css/index.CsLB_Bnz.css +0 -1
  151. streamlit/static/static/js/ProgressBar.EhJ_lCOf.js +0 -2
  152. streamlit/static/static/js/RenderInPortalIfExists.D6a0mMll.js +0 -1
  153. streamlit/static/static/js/Toolbar.D6yqQ65-.js +0 -1
  154. streamlit/static/static/js/createDownloadLinkElement.DZMwyjvU.js +0 -1
  155. streamlit/static/static/js/data-grid-overlay-editor.CoquyZNK.js +0 -1
  156. streamlit/static/static/js/index.B3n-pURl.js +0 -2
  157. streamlit/static/static/js/index.B9jJp9aE.js +0 -1
  158. streamlit/static/static/js/index.BBVtld-D.js +0 -1
  159. streamlit/static/static/js/index.BKHPnvYd.js +0 -1
  160. streamlit/static/static/js/index.B_1CXynz.js +0 -1
  161. streamlit/static/static/js/index.Bm1LklYO.js +0 -1
  162. streamlit/static/static/js/index.Byi6__iF.js +0 -1
  163. streamlit/static/static/js/index.BzJeMpQ-.js +0 -197
  164. streamlit/static/static/js/index.C4tw7-Cl.js +0 -2
  165. streamlit/static/static/js/index.CG0C49ex.js +0 -1
  166. streamlit/static/static/js/index.CMuSJPv-.js +0 -1
  167. streamlit/static/static/js/index.COPFcr_K.js +0 -3855
  168. streamlit/static/static/js/index.CU3TLDlu.js +0 -1
  169. streamlit/static/static/js/index.CVKKDwaf.js +0 -1
  170. streamlit/static/static/js/index.CWZeK3mV.js +0 -12
  171. streamlit/static/static/js/index.CWbiNJQl.js +0 -1
  172. streamlit/static/static/js/index.CWxefYP6.js +0 -73
  173. streamlit/static/static/js/index.DOdWa88b.js +0 -1
  174. streamlit/static/static/js/index.DXtnaPua.js +0 -1
  175. streamlit/static/static/js/index.DatDwFl3.js +0 -3
  176. streamlit/static/static/js/index.DeWYPvQR.js +0 -1
  177. streamlit/static/static/js/index.Do4vzIvK.js +0 -1
  178. streamlit/static/static/js/index.Dvrstlh8.js +0 -1
  179. streamlit/static/static/js/index.DvznfdF_.js +0 -1
  180. streamlit/static/static/js/index.U1vvXeGn.js +0 -1
  181. streamlit/static/static/js/index._WAuWRkp.js +0 -7
  182. streamlit/static/static/js/index.bwA9_eWC.js +0 -1
  183. streamlit/static/static/js/index.m1njXuKl.js +0 -1
  184. streamlit/static/static/js/useOnInputChange.CDZx-L6q.js +0 -1
  185. streamlit/static/static/js/withFullScreenWrapper.DWXejOhQ.js +0 -1
  186. {streamlit-1.47.1.data → streamlit-1.48.0.data}/scripts/streamlit.cmd +0 -0
  187. {streamlit-1.47.1.dist-info → streamlit-1.48.0.dist-info}/WHEEL +0 -0
  188. {streamlit-1.47.1.dist-info → streamlit-1.48.0.dist-info}/entry_points.txt +0 -0
  189. {streamlit-1.47.1.dist-info → streamlit-1.48.0.dist-info}/top_level.txt +0 -0
@@ -21,20 +21,30 @@ from typing_extensions import TypeAlias
21
21
 
22
22
  from streamlit.delta_generator_singletons import get_dg_singleton_instance
23
23
  from streamlit.elements.lib.layout_utils import (
24
+ Gap,
25
+ Height,
26
+ HorizontalAlignment,
27
+ VerticalAlignment,
28
+ Width,
24
29
  WidthWithoutContent,
30
+ get_align,
31
+ get_gap_size,
32
+ get_height_config,
33
+ get_justify,
25
34
  get_width_config,
35
+ validate_height,
36
+ validate_horizontal_alignment,
37
+ validate_vertical_alignment,
26
38
  validate_width,
27
39
  )
28
40
  from streamlit.elements.lib.utils import Key, compute_and_register_element_id, to_key
29
41
  from streamlit.errors import (
30
42
  StreamlitAPIException,
31
- StreamlitInvalidColumnGapError,
32
43
  StreamlitInvalidColumnSpecError,
33
44
  StreamlitInvalidVerticalAlignmentError,
34
45
  )
35
46
  from streamlit.proto.Block_pb2 import Block as BlockProto
36
- from streamlit.proto.GapSize_pb2 import GapConfig, GapSize
37
- from streamlit.proto.HeightConfig_pb2 import HeightConfig
47
+ from streamlit.proto.GapSize_pb2 import GapConfig
38
48
  from streamlit.runtime.metrics_util import gather_metrics
39
49
  from streamlit.string_util import validate_icon_or_emoji
40
50
 
@@ -42,6 +52,7 @@ if TYPE_CHECKING:
42
52
  from streamlit.delta_generator import DeltaGenerator
43
53
  from streamlit.elements.lib.dialog import Dialog
44
54
  from streamlit.elements.lib.mutable_status_container import StatusContainer
55
+ from streamlit.runtime.state import WidgetCallback
45
56
 
46
57
  SpecType: TypeAlias = Union[int, Sequence[Union[int, float]]]
47
58
 
@@ -51,9 +62,14 @@ class LayoutsMixin:
51
62
  def container(
52
63
  self,
53
64
  *,
54
- height: int | None = None,
55
65
  border: bool | None = None,
56
66
  key: Key | None = None,
67
+ width: WidthWithoutContent = "stretch",
68
+ height: Height = "content",
69
+ horizontal: bool = False,
70
+ horizontal_alignment: HorizontalAlignment = "left",
71
+ vertical_alignment: VerticalAlignment = "top",
72
+ gap: Gap | None = "small",
57
73
  ) -> DeltaGenerator:
58
74
  """Insert a multi-element container.
59
75
 
@@ -61,25 +77,12 @@ class LayoutsMixin:
61
77
  multiple elements. This allows you to, for example, insert multiple
62
78
  elements into your app out of order.
63
79
 
64
- To add elements to the returned container, you can use the ``with`` notation
65
- (preferred) or just call methods directly on the returned object. See
66
- examples below.
80
+ To add elements to the returned container, you can use the ``with``
81
+ notation (preferred) or just call commands directly on the returned
82
+ object. See examples below.
67
83
 
68
84
  Parameters
69
85
  ----------
70
- height : int or None
71
- Desired height of the container expressed in pixels. If ``None`` (default)
72
- the container grows to fit its content. If a fixed height, scrolling is
73
- enabled for large content and a grey border is shown around the container
74
- to visually separate its scroll surface from the rest of the app.
75
-
76
- .. note::
77
- Use scrolling containers sparingly. If you use scrolling
78
- containers, avoid heights that exceed 500 pixels. Otherwise,
79
- the scroll surface of the container might cover the majority of
80
- the screen on mobile devices, which makes it hard to scroll the
81
- rest of the app.
82
-
83
86
  border : bool or None
84
87
  Whether to show a border around the container. If ``None`` (default), a
85
88
  border is shown if the container is set to a fixed height and not
@@ -91,10 +94,102 @@ class LayoutsMixin:
91
94
  Additionally, if ``key`` is provided, it will be used as CSS
92
95
  class name prefixed with ``st-key-``.
93
96
 
97
+ width : "stretch" or int
98
+ The width of the container. This can be one of the following:
99
+
100
+ - ``"stretch"`` (default): The width of the container matches the
101
+ width of the parent container.
102
+ - An integer specifying the width in pixels: The container has a
103
+ fixed width. If the specified width is greater than the width of
104
+ the parent container, the width of the container matches the width
105
+ of the parent container.
106
+
107
+ height : "content", "stretch", or int
108
+ The height of the container. This can be one of the following:
109
+
110
+ - ``"content"`` (default): The height of the container matches the
111
+ height of its content.
112
+ - ``"stretch"``: The height of the container matches the height of
113
+ its content or the height of the parent container, whichever is
114
+ larger. If the container is not in a parent container, the height
115
+ of the container matches the height of its content.
116
+ - An integer specifying the height in pixels: The container has a
117
+ fixed height. If the content is larger than the specified
118
+ height, scrolling is enabled.
119
+
120
+ .. note::
121
+ Use scrolling containers sparingly. If you use scrolling
122
+ containers, avoid heights that exceed 500 pixels. Otherwise,
123
+ the scroll surface of the container might cover the majority of
124
+ the screen on mobile devices, which makes it hard to scroll the
125
+ rest of the app.
126
+
127
+ horizontal : bool
128
+ Whether to use horizontal flexbox layout. If this is ``False``
129
+ (default), the container's elements are laid out vertically. If
130
+ this is ``True``, the container's elements are laid out
131
+ horizontally and will overflow to the next line if they don't fit
132
+ within the container's width.
133
+
134
+ horizontal_alignment : "left", "center", "right", or "distribute"
135
+ The horizontal alignment of the elements inside the container. This
136
+ can be one of the following:
137
+
138
+ - ``"left"`` (default): Elements are aligned to the left side of
139
+ the container.
140
+ - ``"center"``: Elements are horizontally centered inside the
141
+ container.
142
+ - ``"right"``: Elements are aligned to the right side of the
143
+ container.
144
+ - ``"distribute"``: Elements are distributed evenly in the
145
+ container. This increases the horizontal gap between elements to
146
+ fill the width of the container. A standalone element is aligned
147
+ to the left.
148
+
149
+ When ``horizontal`` is ``False``, ``"distribute"`` aligns the
150
+ elements the same as ``"left"``.
151
+
152
+ vertical_alignment : "top", "center", "bottom", or "distribute"
153
+ The vertical alignment of the elements inside the container. This
154
+ can be one of the following:
155
+
156
+ - ``"top"`` (default): Elements are aligned to the top of the
157
+ container.
158
+ - ``"center"``: Elements are vertically centered inside the
159
+ container.
160
+ - ``"bottom"``: Elements are aligned to the bottom of the
161
+ container.
162
+ - ``"distribute"``: Elements are distributed evenly in the
163
+ container. This increases the vertical gap between elements to
164
+ fill the height of the container. A standalone element is aligned
165
+ to the top.
166
+
167
+ When ``horizontal`` is ``True``, ``"distribute"`` aligns the
168
+ elements the same as ``"top"``.
169
+
170
+ gap : "small", "medium", "large", or None
171
+ The minimum gap size between the elements inside the container.
172
+ This can be one of the following:
173
+
174
+ - ``"small"`` (default): 1rem gap between the elements.
175
+ - ``"medium"``: 2rem gap between the elements.
176
+ - ``"large"``: 4rem gap between the elements.
177
+ - ``None``: No gap between the elements.
178
+
179
+ The rem unit is relative to the ``theme.baseFontSize``
180
+ configuration option.
181
+
182
+ The minimum gap applies to both the vertical and horizontal gaps
183
+ between the elements. Elements may have larger gaps in one
184
+ direction if you use a distributed horizontal alignment or fixed
185
+ height.
94
186
 
95
187
  Examples
96
188
  --------
97
- Inserting elements using ``with`` notation:
189
+ **Example 1: Inserting elements using ``with`` notation**
190
+
191
+ You can use the ``with`` statement to insert any element into a
192
+ container.
98
193
 
99
194
  >>> import streamlit as st
100
195
  >>>
@@ -110,7 +205,12 @@ class LayoutsMixin:
110
205
  https://doc-container1.streamlit.app/
111
206
  height: 520px
112
207
 
113
- Inserting elements out of order:
208
+ **Example 2: Inserting elements out of order**
209
+
210
+ When you create a container, its position in the app remains fixed and
211
+ you can add elements to it at any time. This allows you to insert
212
+ elements out of order in your app. You can also write to the container
213
+ by calling commands directly on the container object.
114
214
 
115
215
  >>> import streamlit as st
116
216
  >>>
@@ -118,14 +218,16 @@ class LayoutsMixin:
118
218
  >>> container.write("This is inside the container")
119
219
  >>> st.write("This is outside the container")
120
220
  >>>
121
- >>> # Now insert some more in the container
122
221
  >>> container.write("This is inside too")
123
222
 
124
223
  .. output ::
125
224
  https://doc-container2.streamlit.app/
126
225
  height: 300px
127
226
 
128
- Using ``height`` to make a grid:
227
+ **Example 3: Grid layout with columns and containers**
228
+
229
+ You can create a grid with a fixed number of elements per row by using
230
+ columns and containers.
129
231
 
130
232
  >>> import streamlit as st
131
233
  >>>
@@ -140,7 +242,10 @@ class LayoutsMixin:
140
242
  https://doc-container3.streamlit.app/
141
243
  height: 350px
142
244
 
143
- Using ``height`` to create a scrolling container for long content:
245
+ **Example 4: Vertically scrolling container**
246
+
247
+ You can create a vertically scrolling container by setting a fixed
248
+ height.
144
249
 
145
250
  >>> import streamlit as st
146
251
  >>>
@@ -153,28 +258,63 @@ class LayoutsMixin:
153
258
  https://doc-container4.streamlit.app/
154
259
  height: 400px
155
260
 
261
+ **Example 5: Horizontal container**
262
+
263
+ You can create a row of widgets using a horizontal container. Use
264
+ ``horizontal_alignment`` to specify the alignment of the elements.
265
+
266
+ >>> import streamlit as st
267
+ >>>
268
+ >>> flex = st.container(horizontal=True, horizontal_alignment="right")
269
+ >>>
270
+ >>> for card in range(3):
271
+ >>> flex.button(f"Button {card + 1}")
272
+
273
+ .. output ::
274
+ https://doc-container5.streamlit.app/
275
+ height: 250px
276
+
156
277
  """
157
278
  key = to_key(key)
158
279
  block_proto = BlockProto()
159
280
  block_proto.allow_empty = False
160
281
  block_proto.flex_container.border = border or False
161
- block_proto.flex_container.wrap = False
282
+ block_proto.flex_container.gap_config.gap_size = get_gap_size(
283
+ gap, "st.container"
284
+ )
285
+
286
+ validate_horizontal_alignment(horizontal_alignment)
287
+ validate_vertical_alignment(vertical_alignment)
288
+ if horizontal:
289
+ block_proto.flex_container.wrap = True
290
+ block_proto.flex_container.direction = (
291
+ BlockProto.FlexContainer.Direction.HORIZONTAL
292
+ )
293
+ block_proto.flex_container.justify = get_justify(horizontal_alignment)
294
+ block_proto.flex_container.align = get_align(vertical_alignment)
295
+ else:
296
+ block_proto.flex_container.wrap = False
297
+ block_proto.flex_container.direction = (
298
+ BlockProto.FlexContainer.Direction.VERTICAL
299
+ )
300
+ block_proto.flex_container.justify = get_justify(vertical_alignment)
301
+ block_proto.flex_container.align = get_align(horizontal_alignment)
302
+
303
+ validate_width(width)
304
+ block_proto.width_config.CopyFrom(get_width_config(width))
162
305
 
163
306
  if isinstance(height, int) or border:
164
307
  block_proto.allow_empty = True
165
308
 
166
- if height:
167
- # Activate scrolling container behavior:
168
- height_config = HeightConfig()
169
- height_config.pixel_height = height
170
- # Use block-level height_config instead of flex_container
171
- block_proto.height_config.CopyFrom(height_config)
309
+ if border is not None:
310
+ block_proto.flex_container.border = border
311
+ elif isinstance(height, int):
312
+ block_proto.flex_container.border = True
313
+ else:
314
+ block_proto.flex_container.border = False
172
315
 
173
- if border is None:
174
- # If border is None, we activated the
175
- # border as default setting for scrolling
176
- # containers.
177
- block_proto.flex_container.border = True
316
+ validate_height(height, allow_content=True)
317
+ block_proto.height_config.CopyFrom(get_height_config(height))
178
318
 
179
319
  if key:
180
320
  # At the moment, the ID is only used for extracting the
@@ -193,9 +333,10 @@ class LayoutsMixin:
193
333
  self,
194
334
  spec: SpecType,
195
335
  *,
196
- gap: Literal["small", "medium", "large"] | None = "small",
336
+ gap: Gap | None = "small",
197
337
  vertical_alignment: Literal["top", "center", "bottom"] = "top",
198
338
  border: bool = False,
339
+ width: WidthWithoutContent = "stretch",
199
340
  ) -> list[DeltaGenerator]:
200
341
  """Insert containers laid out as side-by-side columns.
201
342
 
@@ -244,6 +385,14 @@ class LayoutsMixin:
244
385
  ``False`` (default), no border is shown. If this is ``True``, a
245
386
  border is shown around each column.
246
387
 
388
+ width : int or "stretch"
389
+ The desired width of the columns expressed in pixels. If this is
390
+ ``"stretch"`` (default), Streamlit sets the width of the columns to
391
+ match the width of the parent container. Otherwise, this must be an
392
+ integer. If the specified width is greater than the width of the
393
+ parent container, Streamlit sets the width of the columns to match
394
+ the width of the parent container.
395
+
247
396
  Returns
248
397
  -------
249
398
  list of containers
@@ -281,16 +430,16 @@ class LayoutsMixin:
281
430
  You can just call methods directly on the returned objects:
282
431
 
283
432
  >>> import streamlit as st
284
- >>> import numpy as np
433
+ >>> from numpy.random import default_rng as rng
285
434
  >>>
435
+ >>> df = rng(0).standard_normal((10, 1))
286
436
  >>> col1, col2 = st.columns([3, 1])
287
- >>> data = np.random.randn(10, 1)
288
437
  >>>
289
438
  >>> col1.subheader("A wide column with a chart")
290
- >>> col1.line_chart(data)
439
+ >>> col1.line_chart(df)
291
440
  >>>
292
441
  >>> col2.subheader("A narrow column with the data")
293
- >>> col2.write(data)
442
+ >>> col2.write(df)
294
443
 
295
444
  .. output ::
296
445
  https://doc-columns2.streamlit.app/
@@ -317,7 +466,6 @@ class LayoutsMixin:
317
466
  Adjust vertical alignment to customize your grid layouts.
318
467
 
319
468
  >>> import streamlit as st
320
- >>> import numpy as np
321
469
  >>>
322
470
  >>> vertical_alignment = st.selectbox(
323
471
  >>> "Vertical alignment", ["top", "center", "bottom"], index=2
@@ -370,28 +518,11 @@ class LayoutsMixin:
370
518
 
371
519
  if vertical_alignment not in vertical_alignment_mapping:
372
520
  raise StreamlitInvalidVerticalAlignmentError(
373
- vertical_alignment=vertical_alignment
521
+ vertical_alignment=vertical_alignment,
522
+ element_type="st.columns",
374
523
  )
375
524
 
376
- def column_gap(gap: str | None) -> GapSize.ValueType:
377
- gap_mapping = {
378
- "small": GapSize.SMALL,
379
- "medium": GapSize.MEDIUM,
380
- "large": GapSize.LARGE,
381
- }
382
-
383
- if isinstance(gap, str):
384
- gap_size = gap.lower()
385
- valid_sizes = gap_mapping.keys()
386
-
387
- if gap_size in valid_sizes:
388
- return gap_mapping[gap_size]
389
- elif gap is None:
390
- return GapSize.NONE
391
-
392
- raise StreamlitInvalidColumnGapError(gap=gap)
393
-
394
- gap_size = column_gap(gap)
525
+ gap_size = get_gap_size(gap, "st.columns")
395
526
  gap_config = GapConfig()
396
527
  gap_config.gap_size = gap_size
397
528
 
@@ -413,6 +544,11 @@ class LayoutsMixin:
413
544
  block_proto.flex_container.wrap = True
414
545
  block_proto.flex_container.gap_config.CopyFrom(gap_config)
415
546
  block_proto.flex_container.scale = 1
547
+ block_proto.flex_container.align = BlockProto.FlexContainer.Align.STRETCH
548
+
549
+ validate_width(width=width)
550
+ block_proto.width_config.CopyFrom(get_width_config(width=width))
551
+
416
552
  row = self.dg._block(block_proto)
417
553
  total_weight = sum(weights)
418
554
  return [row._block(column_proto(w / total_weight)) for w in weights]
@@ -502,17 +638,17 @@ class LayoutsMixin:
502
638
  Or you can just call methods directly on the returned objects:
503
639
 
504
640
  >>> import streamlit as st
505
- >>> import numpy as np
641
+ >>> from numpy.random import default_rng as rng
642
+ >>>
643
+ >>> df = rng(0).standard_normal((10, 1))
506
644
  >>>
507
645
  >>> tab1, tab2 = st.tabs(["📈 Chart", "🗃 Data"])
508
- >>> data = np.random.randn(10, 1)
509
646
  >>>
510
647
  >>> tab1.subheader("A tab with a chart")
511
- >>> tab1.line_chart(data)
648
+ >>> tab1.line_chart(df)
512
649
  >>>
513
650
  >>> tab2.subheader("A tab with the data")
514
- >>> tab2.write(data)
515
-
651
+ >>> tab2.write(df)
516
652
 
517
653
  .. output ::
518
654
  https://doc-tabs2.streamlit.app/
@@ -682,7 +818,8 @@ class LayoutsMixin:
682
818
  help: str | None = None,
683
819
  icon: str | None = None,
684
820
  disabled: bool = False,
685
- use_container_width: bool = False,
821
+ use_container_width: bool | None = None,
822
+ width: Width = "content",
686
823
  ) -> DeltaGenerator:
687
824
  r"""Insert a popover container.
688
825
 
@@ -752,16 +889,39 @@ class LayoutsMixin:
752
889
  use_container_width : bool
753
890
  Whether to expand the button's width to fill its parent container.
754
891
  If ``use_container_width`` is ``False`` (default), Streamlit sizes
755
- the button to fit its contents. If ``use_container_width`` is
892
+ the button to fit its content. If ``use_container_width`` is
756
893
  ``True``, the width of the button matches its parent container.
757
894
 
758
- In both cases, if the contents of the button are wider than the
759
- parent container, the contents will line wrap.
895
+ In both cases, if the content of the button is wider than the
896
+ parent container, the content will line wrap.
760
897
 
761
- The popover containter's minimimun width matches the width of its
898
+ The popover container's minimum width matches the width of its
899
+ button. The popover container may be wider than its button to fit
900
+ the container's content.
901
+
902
+ width : int, "stretch", or "content"
903
+ The width of the button. This can be one of the following:
904
+
905
+ - ``"content"`` (default): The width of the button matches the
906
+ width of its content, but doesn't exceed the width of the parent
907
+ container.
908
+ - ``"stretch"``: The width of the button matches the width of the
909
+ parent container.
910
+ - An integer specifying the width in pixels: The button has a
911
+ fixed width. If the specified width is greater than the width of
912
+ the parent container, the width of the button matches the width
913
+ of the parent container.
914
+
915
+ The popover container's minimum width matches the width of its
762
916
  button. The popover container may be wider than its button to fit
763
917
  the container's contents.
764
918
 
919
+ .. deprecated::
920
+ ``use_container_width`` is deprecated and will be removed in a
921
+ future release. For ``use_container_width=True``, use
922
+ ``width="stretch"``. For ``use_container_width=False``, use
923
+ ``width="content"``.
924
+
765
925
  Examples
766
926
  --------
767
927
  You can use the ``with`` notation to insert any element into a popover:
@@ -799,9 +959,11 @@ class LayoutsMixin:
799
959
  if label is None:
800
960
  raise StreamlitAPIException("A label is required for a popover")
801
961
 
962
+ if use_container_width is not None:
963
+ width = "stretch" if use_container_width else "content"
964
+
802
965
  popover_proto = BlockProto.Popover()
803
966
  popover_proto.label = label
804
- popover_proto.use_container_width = use_container_width
805
967
  popover_proto.disabled = disabled
806
968
  if help:
807
969
  popover_proto.help = str(help)
@@ -812,6 +974,9 @@ class LayoutsMixin:
812
974
  block_proto.allow_empty = True
813
975
  block_proto.popover.CopyFrom(popover_proto)
814
976
 
977
+ validate_width(width, allow_content=True)
978
+ block_proto.width_config.CopyFrom(get_width_config(width))
979
+
815
980
  return self.dg._block(block_proto=block_proto)
816
981
 
817
982
  @gather_metrics("status")
@@ -948,6 +1113,7 @@ class LayoutsMixin:
948
1113
  *,
949
1114
  dismissible: bool = True,
950
1115
  width: Literal["small", "large"] = "small",
1116
+ on_dismiss: Literal["ignore", "rerun"] | WidgetCallback = "ignore",
951
1117
  ) -> Dialog:
952
1118
  """Inserts the dialog container.
953
1119
 
@@ -955,7 +1121,7 @@ class LayoutsMixin:
955
1121
  The dialog_decorator also has a more descriptive docstring since it is user-facing.
956
1122
  """
957
1123
  return get_dg_singleton_instance().dialog_container_cls._create(
958
- self.dg, title, dismissible=dismissible, width=width
1124
+ self.dg, title, dismissible=dismissible, width=width, on_dismiss=on_dismiss
959
1125
  )
960
1126
 
961
1127
  @property
@@ -356,7 +356,13 @@ def prep_chart_data_for_add_rows(
356
356
  df.index = pd.RangeIndex(start=start, stop=stop, step=old_step)
357
357
  add_rows_metadata.last_index = stop - 1
358
358
 
359
- out_data, *_ = _prep_data(df, **add_rows_metadata.columns)
359
+ out_data, *_ = _prep_data(
360
+ df,
361
+ x_column=add_rows_metadata.columns["x_column"],
362
+ y_column_list=add_rows_metadata.columns["y_column_list"],
363
+ color_column=add_rows_metadata.columns["color_column"],
364
+ size_column=add_rows_metadata.columns["size_column"],
365
+ )
360
366
 
361
367
  return out_data, add_rows_metadata
362
368
 
@@ -597,7 +603,7 @@ def _maybe_reset_index_in_place(
597
603
  x_column = _SEPARATED_INDEX_COLUMN_NAME
598
604
  else:
599
605
  # Reuse index's name for the new column.
600
- x_column = df.index.name
606
+ x_column = str(df.index.name)
601
607
 
602
608
  df.index.name = x_column
603
609
  df.reset_index(inplace=True) # noqa: PD002
@@ -988,7 +994,9 @@ def _get_color_encoding(
988
994
  return alt.ColorValue(to_css_color(cast("Any", color_value[0])))
989
995
  return alt.Color(
990
996
  field=color_column if color_column is not None else alt.Undefined,
991
- scale=alt.Scale(range=[to_css_color(c) for c in color_values]),
997
+ scale=alt.Scale(
998
+ domain=y_column_list, range=[to_css_color(c) for c in color_values]
999
+ ),
992
1000
  legend=_COLOR_LEGEND_SETTINGS,
993
1001
  type="nominal",
994
1002
  title=" ",
@@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, Literal, cast
19
19
  from typing_extensions import Self, TypeAlias
20
20
 
21
21
  from streamlit.delta_generator import DeltaGenerator
22
+ from streamlit.elements.lib.utils import compute_and_register_element_id
22
23
  from streamlit.errors import StreamlitAPIException
23
24
  from streamlit.proto.Block_pb2 import Block as BlockProto
24
25
  from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
@@ -26,11 +27,13 @@ from streamlit.runtime.scriptrunner_utils.script_run_context import (
26
27
  enqueue_message,
27
28
  get_script_run_ctx,
28
29
  )
30
+ from streamlit.runtime.state import register_widget
29
31
 
30
32
  if TYPE_CHECKING:
31
33
  from types import TracebackType
32
34
 
33
35
  from streamlit.cursor import Cursor
36
+ from streamlit.runtime.state import WidgetCallback
34
37
 
35
38
  DialogWidth: TypeAlias = Literal["small", "large"]
36
39
 
@@ -81,12 +84,51 @@ class Dialog(DeltaGenerator):
81
84
  *,
82
85
  dismissible: bool = True,
83
86
  width: DialogWidth = "small",
87
+ on_dismiss: Literal["ignore", "rerun"] | WidgetCallback = "ignore",
84
88
  ) -> Dialog:
89
+ # Validation for on_dismiss parameter
90
+ if on_dismiss not in ["ignore", "rerun"] and not callable(on_dismiss):
91
+ raise StreamlitAPIException(
92
+ f"You have passed {on_dismiss} to `on_dismiss`. But only 'ignore', "
93
+ "'rerun', or a callable is supported."
94
+ )
95
+
85
96
  block_proto = BlockProto()
86
97
  block_proto.dialog.title = title
87
98
  block_proto.dialog.dismissible = dismissible
88
99
  block_proto.dialog.width = _process_dialog_width_input(width)
89
100
 
101
+ # Handle on_dismiss functionality
102
+ is_dismiss_activated = on_dismiss != "ignore"
103
+ element_id = None
104
+
105
+ if is_dismiss_activated:
106
+ # Register as widget when on_dismiss is activated
107
+
108
+ ctx = get_script_run_ctx()
109
+
110
+ element_id = compute_and_register_element_id(
111
+ "dialog",
112
+ user_key=None,
113
+ # Dialogs within forms still trigger a normal rerun:
114
+ form_id="",
115
+ dg=parent,
116
+ title=title,
117
+ dismissible=dismissible,
118
+ width=width,
119
+ on_dismiss=str(on_dismiss) if not callable(on_dismiss) else "callback",
120
+ )
121
+ block_proto.dialog.id = element_id
122
+
123
+ register_widget(
124
+ element_id,
125
+ on_change_handler=on_dismiss if callable(on_dismiss) else None,
126
+ deserializer=lambda x: x, # Simple passthrough for trigger values
127
+ serializer=lambda x: x, # Simple passthrough for trigger values
128
+ ctx=ctx,
129
+ value_type="trigger_value",
130
+ )
131
+
90
132
  # We store the delta path here, because in _update we enqueue a new proto
91
133
  # message to update the open status. Without this, the dialog content is gone
92
134
  # when the _update message is sent
@@ -97,6 +139,7 @@ class Dialog(DeltaGenerator):
97
139
 
98
140
  dialog._delta_path = delta_path
99
141
  dialog._current_proto = block_proto
142
+
100
143
  return dialog
101
144
 
102
145
  def __init__(
@@ -31,6 +31,18 @@ TYPE_PAIRS = [
31
31
  ]
32
32
 
33
33
 
34
+ def _get_main_filename_and_extension(filename: str) -> tuple[str, str]:
35
+ """Returns the main part of a filename and its extension."""
36
+ # Handle NTFS Alternate Data Streams (ADS) on Windows, e.g: "file.txt:ads" -> ("file.txt", ".txt")
37
+ if os.name == "nt" and ":" in filename:
38
+ main_filename, ads_part = filename.split(":", 1)
39
+ # We only treat it as an ADS if the part after the colon has an extension.
40
+ if os.path.splitext(ads_part)[1]:
41
+ return main_filename, os.path.splitext(main_filename)[1]
42
+
43
+ return filename, os.path.splitext(filename)[1]
44
+
45
+
34
46
  def normalize_upload_file_type(file_type: str | Sequence[str]) -> Sequence[str]:
35
47
  if isinstance(file_type, str):
36
48
  file_type = [file_type]
@@ -59,8 +71,15 @@ def enforce_filename_restriction(filename: str, allowed_types: Sequence[str]) ->
59
71
  enforce file type check by extension on the frontend, but we check it on backend
60
72
  before returning file to the user to protect ourselves.
61
73
  """
62
- normalized_filename = filename.lower()
63
- base_name, extension = os.path.splitext(normalized_filename)
74
+
75
+ # Ensure that there isn't a null byte in a filename
76
+ # since this could be a workaround to bypass the file type check.
77
+ if "\0" in filename:
78
+ raise StreamlitAPIException("Filename cannot contain null bytes.")
79
+
80
+ main_filename, extension = _get_main_filename_and_extension(filename)
81
+ normalized_filename = main_filename.lower()
82
+
64
83
  normalized_allowed_types = [allowed_type.lower() for allowed_type in allowed_types]
65
84
 
66
85
  if not any(