streamlit 1.51.0__py3-none-any.whl → 1.52.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 (314) hide show
  1. streamlit/__init__.py +1 -0
  2. streamlit/commands/execution_control.py +89 -14
  3. streamlit/components/v1/component_arrow.py +7 -7
  4. streamlit/components/v2/__init__.py +59 -3
  5. streamlit/components/v2/bidi_component/main.py +161 -13
  6. streamlit/components/v2/bidi_component/serialization.py +13 -6
  7. streamlit/components/v2/component_manager.py +11 -3
  8. streamlit/components/v2/component_registry.py +18 -1
  9. streamlit/components/v2/types.py +2 -2
  10. streamlit/connections/snowflake_connection.py +1 -1
  11. streamlit/connections/snowpark_connection.py +1 -1
  12. streamlit/dataframe_util.py +18 -18
  13. streamlit/delta_generator.py +7 -0
  14. streamlit/delta_generator_singletons.py +8 -14
  15. streamlit/elements/alert.py +16 -0
  16. streamlit/elements/arrow.py +36 -6
  17. streamlit/elements/bokeh_chart.py +10 -78
  18. streamlit/elements/code.py +2 -2
  19. streamlit/elements/deck_gl_json_chart.py +1 -1
  20. streamlit/elements/exception.py +1 -1
  21. streamlit/elements/form.py +27 -0
  22. streamlit/elements/heading.py +60 -5
  23. streamlit/elements/html.py +13 -2
  24. streamlit/elements/image.py +1 -1
  25. streamlit/elements/layouts.py +28 -22
  26. streamlit/elements/lib/built_in_chart_utils.py +49 -16
  27. streamlit/elements/lib/color_util.py +1 -1
  28. streamlit/elements/lib/column_config_utils.py +6 -5
  29. streamlit/elements/lib/layout_utils.py +50 -0
  30. streamlit/elements/lib/pandas_styler_utils.py +17 -9
  31. streamlit/elements/lib/shortcut_utils.py +152 -0
  32. streamlit/elements/markdown.py +50 -3
  33. streamlit/elements/metric.py +31 -1
  34. streamlit/elements/plotly_chart.py +75 -6
  35. streamlit/elements/spinner.py +1 -1
  36. streamlit/elements/text.py +20 -3
  37. streamlit/elements/toast.py +2 -0
  38. streamlit/elements/vega_charts.py +17 -1
  39. streamlit/elements/widgets/audio_input.py +8 -7
  40. streamlit/elements/widgets/button.py +279 -40
  41. streamlit/elements/widgets/button_group.py +27 -2
  42. streamlit/elements/widgets/camera_input.py +1 -1
  43. streamlit/elements/widgets/chat.py +300 -42
  44. streamlit/elements/widgets/color_picker.py +7 -0
  45. streamlit/elements/widgets/data_editor.py +68 -28
  46. streamlit/elements/widgets/file_uploader.py +4 -1
  47. streamlit/elements/widgets/number_input.py +2 -0
  48. streamlit/elements/widgets/text_widgets.py +2 -0
  49. streamlit/elements/widgets/time_widgets.py +581 -9
  50. streamlit/errors.py +22 -0
  51. streamlit/git_util.py +1 -1
  52. streamlit/navigation/page.py +7 -0
  53. streamlit/net_util.py +2 -2
  54. streamlit/proto/Alert_pb2.pyi +3 -3
  55. streamlit/proto/AppPage_pb2.pyi +7 -1
  56. streamlit/proto/ArrowData_pb2.pyi +7 -1
  57. streamlit/proto/ArrowNamedDataSet_pb2.pyi +7 -1
  58. streamlit/proto/ArrowVegaLiteChart_pb2.pyi +7 -1
  59. streamlit/proto/Arrow_pb2.py +10 -10
  60. streamlit/proto/Arrow_pb2.pyi +19 -12
  61. streamlit/proto/AudioInput_pb2.pyi +7 -1
  62. streamlit/proto/Audio_pb2.pyi +7 -1
  63. streamlit/proto/AuthRedirect_pb2.pyi +7 -1
  64. streamlit/proto/AutoRerun_pb2.pyi +7 -1
  65. streamlit/proto/BackMsg_pb2.py +4 -2
  66. streamlit/proto/BackMsg_pb2.pyi +34 -4
  67. streamlit/proto/Balloons_pb2.pyi +7 -1
  68. streamlit/proto/BidiComponent_pb2.pyi +10 -4
  69. streamlit/proto/Block_pb2.pyi +35 -35
  70. streamlit/proto/BokehChart_pb2.pyi +7 -1
  71. streamlit/proto/ButtonGroup_pb2.pyi +9 -9
  72. streamlit/proto/Button_pb2.py +2 -2
  73. streamlit/proto/Button_pb2.pyi +11 -2
  74. streamlit/proto/CameraInput_pb2.pyi +7 -1
  75. streamlit/proto/ChatInput_pb2.py +6 -6
  76. streamlit/proto/ChatInput_pb2.pyi +18 -6
  77. streamlit/proto/Checkbox_pb2.pyi +3 -3
  78. streamlit/proto/ClientState_pb2.pyi +10 -4
  79. streamlit/proto/Code_pb2.pyi +7 -1
  80. streamlit/proto/ColorPicker_pb2.pyi +7 -1
  81. streamlit/proto/Common_pb2.py +3 -3
  82. streamlit/proto/Common_pb2.pyi +35 -23
  83. streamlit/proto/Components_pb2.pyi +19 -13
  84. streamlit/proto/DataFrame_pb2.pyi +55 -49
  85. streamlit/proto/DateInput_pb2.pyi +7 -1
  86. streamlit/proto/DateTimeInput_pb2.py +28 -0
  87. streamlit/proto/DateTimeInput_pb2.pyi +92 -0
  88. streamlit/proto/DeckGlJsonChart_pb2.pyi +3 -3
  89. streamlit/proto/Delta_pb2.pyi +7 -1
  90. streamlit/proto/DocString_pb2.pyi +10 -4
  91. streamlit/proto/DownloadButton_pb2.py +2 -2
  92. streamlit/proto/DownloadButton_pb2.pyi +16 -2
  93. streamlit/proto/Element_pb2.py +5 -3
  94. streamlit/proto/Element_pb2.pyi +23 -5
  95. streamlit/proto/Empty_pb2.pyi +7 -1
  96. streamlit/proto/Exception_pb2.pyi +7 -1
  97. streamlit/proto/Favicon_pb2.pyi +7 -1
  98. streamlit/proto/FileUploader_pb2.pyi +7 -1
  99. streamlit/proto/ForwardMsg_pb2.py +12 -10
  100. streamlit/proto/ForwardMsg_pb2.pyi +42 -15
  101. streamlit/proto/GapSize_pb2.pyi +4 -4
  102. streamlit/proto/GitInfo_pb2.pyi +3 -3
  103. streamlit/proto/GraphVizChart_pb2.pyi +7 -1
  104. streamlit/proto/Heading_pb2.pyi +7 -1
  105. streamlit/proto/HeightConfig_pb2.pyi +7 -1
  106. streamlit/proto/Html_pb2.py +2 -2
  107. streamlit/proto/Html_pb2.pyi +11 -2
  108. streamlit/proto/IFrame_pb2.pyi +7 -1
  109. streamlit/proto/Image_pb2.pyi +10 -4
  110. streamlit/proto/Json_pb2.pyi +7 -1
  111. streamlit/proto/LabelVisibilityMessage_pb2.pyi +3 -3
  112. streamlit/proto/LinkButton_pb2.py +2 -2
  113. streamlit/proto/LinkButton_pb2.pyi +15 -2
  114. streamlit/proto/Logo_pb2.pyi +7 -1
  115. streamlit/proto/Markdown_pb2.pyi +3 -3
  116. streamlit/proto/Metric_pb2.pyi +7 -7
  117. streamlit/proto/MetricsEvent_pb2.pyi +10 -4
  118. streamlit/proto/MultiSelect_pb2.pyi +7 -1
  119. streamlit/proto/NamedDataSet_pb2.pyi +7 -1
  120. streamlit/proto/Navigation_pb2.pyi +3 -3
  121. streamlit/proto/NewSession_pb2.pyi +40 -40
  122. streamlit/proto/NumberInput_pb2.pyi +3 -3
  123. streamlit/proto/PageConfig_pb2.pyi +7 -7
  124. streamlit/proto/PageInfo_pb2.pyi +7 -1
  125. streamlit/proto/PageLink_pb2.py +2 -2
  126. streamlit/proto/PageLink_pb2.pyi +11 -2
  127. streamlit/proto/PageNotFound_pb2.pyi +7 -1
  128. streamlit/proto/PageProfile_pb2.pyi +13 -7
  129. streamlit/proto/PagesChanged_pb2.pyi +7 -1
  130. streamlit/proto/ParentMessage_pb2.pyi +7 -1
  131. streamlit/proto/PlotlyChart_pb2.pyi +6 -6
  132. streamlit/proto/Progress_pb2.pyi +7 -1
  133. streamlit/proto/Radio_pb2.pyi +7 -1
  134. streamlit/proto/RootContainer_pb2.pyi +1 -1
  135. streamlit/proto/Selectbox_pb2.pyi +7 -1
  136. streamlit/proto/SessionEvent_pb2.pyi +7 -1
  137. streamlit/proto/SessionStatus_pb2.pyi +7 -1
  138. streamlit/proto/Skeleton_pb2.pyi +3 -3
  139. streamlit/proto/Slider_pb2.pyi +5 -5
  140. streamlit/proto/Snow_pb2.pyi +7 -1
  141. streamlit/proto/Space_pb2.pyi +7 -1
  142. streamlit/proto/Spinner_pb2.pyi +7 -1
  143. streamlit/proto/TextAlignmentConfig_pb2.py +29 -0
  144. streamlit/proto/TextAlignmentConfig_pb2.pyi +68 -0
  145. streamlit/proto/TextArea_pb2.pyi +7 -1
  146. streamlit/proto/TextInput_pb2.pyi +3 -3
  147. streamlit/proto/Text_pb2.pyi +7 -1
  148. streamlit/proto/TimeInput_pb2.pyi +7 -1
  149. streamlit/proto/Toast_pb2.pyi +7 -1
  150. streamlit/proto/VegaLiteChart_pb2.pyi +7 -1
  151. streamlit/proto/Video_pb2.pyi +6 -6
  152. streamlit/proto/WidgetStates_pb2.pyi +10 -4
  153. streamlit/proto/WidthConfig_pb2.pyi +7 -1
  154. streamlit/proto/openmetrics_data_model_pb2.pyi +52 -52
  155. streamlit/runtime/app_session.py +38 -1
  156. streamlit/runtime/caching/cache_data_api.py +1 -1
  157. streamlit/runtime/caching/cache_resource_api.py +2 -2
  158. streamlit/runtime/caching/cache_utils.py +1 -1
  159. streamlit/runtime/caching/hashing.py +1 -1
  160. streamlit/runtime/download_data_util.py +53 -0
  161. streamlit/runtime/forward_msg_queue.py +1 -0
  162. streamlit/runtime/media_file_manager.py +178 -2
  163. streamlit/runtime/metrics_util.py +87 -3
  164. streamlit/runtime/scriptrunner/script_runner.py +3 -1
  165. streamlit/runtime/state/query_params.py +80 -29
  166. streamlit/runtime/state/session_state.py +2 -2
  167. streamlit/static/index.html +1 -1
  168. streamlit/static/manifest.json +530 -229
  169. streamlit/static/static/js/{ErrorOutline.esm.YoJdlW1p.js → ErrorOutline.esm.ZJDbmVTx.js} +1 -1
  170. streamlit/static/static/js/{FileDownload.esm.Ddx8VEYy.js → FileDownload.esm.Dx0vI3vH.js} +1 -1
  171. streamlit/static/static/js/{FileHelper.90EtOmj9.js → FileHelper.B7Ero7qQ.js} +3 -3
  172. streamlit/static/static/js/{FormClearHelper.BB1Km6eP.js → FormClearHelper.CG2XN1_g.js} +1 -1
  173. streamlit/static/static/js/IFrameUtil.DefezniK.js +1 -0
  174. streamlit/static/static/js/InputInstructions.Cj5-1zf6.js +1 -0
  175. streamlit/static/static/js/Particles.BfWfv0Aw.js +1 -0
  176. streamlit/static/static/js/{ProgressBar.DLY8H6nE.js → ProgressBar.CGQ8OgfO.js} +2 -2
  177. streamlit/static/static/js/StreamlitSyntaxHighlighter.DTKLpwhl.js +20 -0
  178. streamlit/static/static/js/{Toolbar.D8nHCkuz.js → Toolbar.B2qFUmd9.js} +1 -1
  179. streamlit/static/static/js/_arrayIncludes.B19Iyn2B.js +1 -0
  180. streamlit/static/static/js/_baseIndexOf.BTknn6Gb.js +1 -0
  181. streamlit/static/static/js/{base-input.CJGiNqed.js → base-input.o9tL8MDP.js} +4 -4
  182. streamlit/static/static/js/{checkbox.Cpdd482O.js → checkbox.0BeV1IBL.js} +1 -1
  183. streamlit/static/static/js/{createSuper.CuQIogbW.js → createSuper.RBO59fEm.js} +1 -1
  184. streamlit/static/static/js/data-grid-overlay-editor.CiTkUy0t.js +1 -0
  185. streamlit/static/static/js/{downloader.CN0K7xlu.js → downloader.DwNZg3Mw.js} +1 -1
  186. streamlit/static/static/js/embed.XT9xNd3F.js +195 -0
  187. streamlit/static/static/js/{es6.BJcsVXQ0.js → es6.x9KsYQg-.js} +2 -2
  188. streamlit/static/static/js/{iframeResizer.contentWindow.XzUvQqcZ.js → iframeResizer.contentWindow.ZVXpMPi0.js} +1 -1
  189. streamlit/static/static/js/index.5VPOamri.js +1 -0
  190. streamlit/static/static/js/index.8HslT92O.js +14 -0
  191. streamlit/static/static/js/index.AnXMIBz3.js +7 -0
  192. streamlit/static/static/js/index.B0yp3bM1.js +6 -0
  193. streamlit/static/static/js/index.B1fRb5wF.js +1 -0
  194. streamlit/static/static/js/index.B527JZdO.js +3 -0
  195. streamlit/static/static/js/index.BHgV-yW4.js +1 -0
  196. streamlit/static/static/js/index.BQr-XwGV.js +1 -0
  197. streamlit/static/static/js/index.BTtmaLDB.js +1 -0
  198. streamlit/static/static/js/index.BWB_91TA.js +1 -0
  199. streamlit/static/static/js/index.BfEKaEmw.js +1 -0
  200. streamlit/static/static/js/index.BfXjTO8b.js +1 -0
  201. streamlit/static/static/js/index.Bjy4NRu9.js +3 -0
  202. streamlit/static/static/js/index.Bu5JWpT_.js +1 -0
  203. streamlit/static/static/js/index.BuCx76ZV.js +1 -0
  204. streamlit/static/static/js/index.BxjzhVUb.js +2 -0
  205. streamlit/static/static/js/index.By55VdPY.js +1 -0
  206. streamlit/static/static/js/index.CF5MxTbK.js +1 -0
  207. streamlit/static/static/js/index.CLmq_z9K.js +1 -0
  208. streamlit/static/static/js/index.CNH4rdSz.js +1 -0
  209. streamlit/static/static/js/{index.D3GPA5k4.js → index.CTgm_-jO.js} +9 -40
  210. streamlit/static/static/js/index.C_rK-Swb.js +188 -0
  211. streamlit/static/static/js/index.CjozwSzS.js +1 -0
  212. streamlit/static/static/js/{index.DOFlg3dS.js → index.CkGVt6-G.js} +1 -1
  213. streamlit/static/static/js/index.CuvXOyER.js +2 -0
  214. streamlit/static/static/js/{index.B_dWA3vd.js → index.CyUHWoCC.js} +2 -2
  215. streamlit/static/static/js/index.CyroQtI4.js +2 -0
  216. streamlit/static/static/js/index.D6HmkoDm.js +263 -0
  217. streamlit/static/static/js/index.DAqCNvsO.js +1 -0
  218. streamlit/static/static/js/index.DB_w_CZQ.js +1 -0
  219. streamlit/static/static/js/index.DBalctjj.js +2 -0
  220. streamlit/static/static/js/index.DK0RFJUG.js +11 -0
  221. streamlit/static/static/js/index.DMxc2XFp.js +151 -0
  222. streamlit/static/static/js/index.DO5utP74.js +2 -0
  223. streamlit/static/static/js/index.DS7lf09n.js +1 -0
  224. streamlit/static/static/js/index.DWexTVLY.js +1 -0
  225. streamlit/static/static/js/index.DXxnU5ej.js +1 -0
  226. streamlit/static/static/js/index.DcU3uDvB.js +2 -0
  227. streamlit/static/static/js/index.DlltaH7J.js +1 -0
  228. streamlit/static/static/js/index.DpNTZz82.js +27 -0
  229. streamlit/static/static/js/index.Dr9HIhQw.js +1 -0
  230. streamlit/static/static/js/index.DsgAU5lc.js +1 -0
  231. streamlit/static/static/js/{index.DPUXkcQL.js → index.KfXqjDYy.js} +1 -1
  232. streamlit/static/static/js/index.PaidgjCs.js +1 -0
  233. streamlit/static/static/js/index.RJZuWCGA.js +1 -0
  234. streamlit/static/static/js/{index.CxIUUfab.js → index.hbeqcRTn.js} +53 -122
  235. streamlit/static/static/js/index.q5hIQwAY.js +1 -0
  236. streamlit/static/static/js/index.rORSX6IW.js +1 -0
  237. streamlit/static/static/js/index.uSX757_v.js +1 -0
  238. streamlit/static/static/js/index.x_QRaLMd.js +1 -0
  239. streamlit/static/static/js/{input.D4MN_FzN.js → input.D5oh9-aB.js} +2 -2
  240. streamlit/static/static/js/main.q9oGOg0H.js +13 -0
  241. streamlit/static/static/js/{memory.DrZjtdGT.js → memory.5kCSFUJS.js} +1 -1
  242. streamlit/static/static/js/moment.C3j7ZXd7.js +4 -0
  243. streamlit/static/static/js/number-overlay-editor.Cn_LsK8N.js +9 -0
  244. streamlit/static/static/js/pandasStylerUtils.BqhXt51_.js +1 -0
  245. streamlit/static/static/js/{possibleConstructorReturn.exeeJQEP.js → possibleConstructorReturn.DD9NK1Z8.js} +1 -1
  246. streamlit/static/static/js/{sandbox.ClO3IuUr.js → sandbox.DACSyz29.js} +1 -1
  247. streamlit/static/static/js/styled-components.C3R090At.js +1 -0
  248. streamlit/static/static/js/threshold.Q1mXg5rX.js +1 -0
  249. streamlit/static/static/js/throttle.B0GR3Iyz.js +1 -0
  250. streamlit/static/static/js/{timepicker.DAhu-vcF.js → timepicker.BdhzPxrv.js} +1 -1
  251. streamlit/static/static/js/timer.C2hYhUse.js +1 -0
  252. streamlit/static/static/js/{toConsumableArray.DNbljYEC.js → toConsumableArray.Db2pdqM2.js} +1 -1
  253. streamlit/static/static/js/uniqueId.CtqIr-Yh.js +1 -0
  254. streamlit/static/static/js/urls.BwSlolu9.js +1 -0
  255. streamlit/static/static/js/{useBasicWidgetState.D6sOH6oI.js → useBasicWidgetState.Bfp6TnSw.js} +1 -1
  256. streamlit/static/static/js/useIntlLocale.hRV75Xgj.js +12 -0
  257. streamlit/static/static/js/{useTextInputAutoExpand.4u3_GcuN.js → useTextInputAutoExpand.QepX7n8Y.js} +1 -1
  258. streamlit/static/static/js/useUpdateUiValue.DHx8TzX6.js +1 -0
  259. streamlit/static/static/js/useWaveformController.WxVzpzEX.js +1 -0
  260. streamlit/static/static/js/value.B4vHRSi7.js +1 -0
  261. streamlit/static/static/js/withCalculatedWidth.DcKeRSWJ.js +1 -0
  262. streamlit/static/static/js/withFullScreenWrapper.CrHddARq.js +1 -0
  263. streamlit/string_util.py +8 -1
  264. streamlit/testing/v1/app_test.py +15 -0
  265. streamlit/testing/v1/element_tree.py +62 -0
  266. streamlit/web/bootstrap.py +24 -0
  267. streamlit/web/server/oauth_authlib_routes.py +5 -2
  268. streamlit/web/server/upload_file_request_handler.py +16 -0
  269. {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/METADATA +9 -5
  270. {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/RECORD +274 -239
  271. streamlit/static/static/js/InputInstructions.jhH15PqV.js +0 -1
  272. streamlit/static/static/js/Particles.DUsputn1.js +0 -1
  273. streamlit/static/static/js/data-grid-overlay-editor.2Ufgxc6y.js +0 -1
  274. streamlit/static/static/js/index.B1ZQh4P1.js +0 -1
  275. streamlit/static/static/js/index.BKstZk0M.js +0 -27
  276. streamlit/static/static/js/index.BMcFsUee.js +0 -1
  277. streamlit/static/static/js/index.BR-IdcTb.js +0 -2
  278. streamlit/static/static/js/index.BgnZEMVh.js +0 -1
  279. streamlit/static/static/js/index.BohqXifI.js +0 -1
  280. streamlit/static/static/js/index.Br5nxKNj.js +0 -2
  281. streamlit/static/static/js/index.BrIKVbNc.js +0 -3
  282. streamlit/static/static/js/index.BtWUPzle.js +0 -1
  283. streamlit/static/static/js/index.C0RLraek.js +0 -1
  284. streamlit/static/static/js/index.CAIjskgG.js +0 -1
  285. streamlit/static/static/js/index.CAj-7vWz.js +0 -949
  286. streamlit/static/static/js/index.CMtEit2O.js +0 -1
  287. streamlit/static/static/js/index.CkRlykEE.js +0 -12
  288. streamlit/static/static/js/index.CmN3FXfI.js +0 -1617
  289. streamlit/static/static/js/index.CwbFI1_-.js +0 -1
  290. streamlit/static/static/js/index.D2KPNy7e.js +0 -1
  291. streamlit/static/static/js/index.DGAh7DMq.js +0 -1
  292. streamlit/static/static/js/index.DKb_NvmG.js +0 -197
  293. streamlit/static/static/js/index.DMqgUYKq.js +0 -1
  294. streamlit/static/static/js/index.DX1xY89g.js +0 -1
  295. streamlit/static/static/js/index.DYATBCsq.js +0 -2
  296. streamlit/static/static/js/index.DaSmGJ76.js +0 -3
  297. streamlit/static/static/js/index.Dd7bMeLP.js +0 -1
  298. streamlit/static/static/js/index.DjmmgI5U.js +0 -1
  299. streamlit/static/static/js/index.Dq56CyM2.js +0 -1
  300. streamlit/static/static/js/index.DuiXaS5_.js +0 -7
  301. streamlit/static/static/js/index.DvFidMLe.js +0 -2
  302. streamlit/static/static/js/index.DwkhC5Pc.js +0 -1
  303. streamlit/static/static/js/index.Q-3sFn1v.js +0 -1
  304. streamlit/static/static/js/index.QJ5QO9sJ.js +0 -1
  305. streamlit/static/static/js/index.VwTaeety.js +0 -1
  306. streamlit/static/static/js/index.YOqQbeX8.js +0 -1
  307. streamlit/static/static/js/number-overlay-editor.DRwAw1In.js +0 -9
  308. streamlit/static/static/js/uniqueId.oG4Gvj1v.js +0 -1
  309. streamlit/static/static/js/useUpdateUiValue.F2R3eTeR.js +0 -1
  310. streamlit/static/static/js/withFullScreenWrapper.zothJIsI.js +0 -1
  311. {streamlit-1.51.0.data → streamlit-1.52.0.data}/scripts/streamlit.cmd +0 -0
  312. {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/WHEEL +0 -0
  313. {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/entry_points.txt +0 -0
  314. {streamlit-1.51.0.dist-info → streamlit-1.52.0.dist-info}/top_level.txt +0 -0
@@ -25,7 +25,7 @@ from streamlit.string_util import clean_text
25
25
 
26
26
  if TYPE_CHECKING:
27
27
  from streamlit.delta_generator import DeltaGenerator
28
- from streamlit.elements.lib.layout_utils import Width
28
+ from streamlit.elements.lib.layout_utils import TextAlignment, Width
29
29
  from streamlit.type_util import SupportsStr
30
30
 
31
31
 
@@ -49,6 +49,7 @@ class HeadingMixin:
49
49
  help: str | None = None,
50
50
  divider: Divider = False,
51
51
  width: Width = "stretch",
52
+ text_alignment: TextAlignment = "left",
52
53
  ) -> DeltaGenerator:
53
54
  """Display text in header formatting.
54
55
 
@@ -97,6 +98,22 @@ class HeadingMixin:
97
98
  the parent container, the width of the element matches the width
98
99
  of the parent container.
99
100
 
101
+ text_alignment : "left", "center", "right", or "justify"
102
+ The horizontal alignment of the text within the element. This can
103
+ be one of the following:
104
+
105
+ - ``"left"`` (default): Text is aligned to the left edge.
106
+ - ``"center"``: Text is centered.
107
+ - ``"right"``: Text is aligned to the right edge.
108
+ - ``"justify"``: Text is justified (stretched to fill the available
109
+ width with the last line left-aligned).
110
+
111
+ .. note::
112
+ For text alignment to have a visible effect, the element's
113
+ width must be wider than its content. If you use
114
+ ``width="content"`` with short text, the alignment may not be
115
+ noticeable.
116
+
100
117
  Examples
101
118
  --------
102
119
  >>> import streamlit as st
@@ -115,7 +132,7 @@ class HeadingMixin:
115
132
 
116
133
  """
117
134
  validate_width(width, allow_content=True)
118
- layout_config = LayoutConfig(width=width)
135
+ layout_config = LayoutConfig(width=width, text_alignment=text_alignment)
119
136
 
120
137
  return self.dg._enqueue(
121
138
  "heading",
@@ -138,6 +155,7 @@ class HeadingMixin:
138
155
  help: str | None = None,
139
156
  divider: Divider = False,
140
157
  width: Width = "stretch",
158
+ text_alignment: TextAlignment = "left",
141
159
  ) -> DeltaGenerator:
142
160
  """Display text in subheader formatting.
143
161
 
@@ -186,6 +204,22 @@ class HeadingMixin:
186
204
  the parent container, the width of the element matches the width
187
205
  of the parent container.
188
206
 
207
+ text_alignment : "left", "center", "right", or "justify"
208
+ The horizontal alignment of the text within the element. This can
209
+ be one of the following:
210
+
211
+ - ``"left"`` (default): Text is aligned to the left edge.
212
+ - ``"center"``: Text is centered.
213
+ - ``"right"``: Text is aligned to the right edge.
214
+ - ``"justify"``: Text is justified (stretched to fill the available
215
+ width with the last line left-aligned).
216
+
217
+ .. note::
218
+ For text alignment to have a visible effect, the element's
219
+ width must be wider than its content. If you use
220
+ ``width="content"`` with short text, the alignment may not be
221
+ noticeable.
222
+
189
223
  Examples
190
224
  --------
191
225
  >>> import streamlit as st
@@ -204,7 +238,7 @@ class HeadingMixin:
204
238
 
205
239
  """
206
240
  validate_width(width, allow_content=True)
207
- layout_config = LayoutConfig(width=width)
241
+ layout_config = LayoutConfig(width=width, text_alignment=text_alignment)
208
242
 
209
243
  return self.dg._enqueue(
210
244
  "heading",
@@ -226,6 +260,7 @@ class HeadingMixin:
226
260
  *, # keyword-only arguments:
227
261
  help: str | None = None,
228
262
  width: Width = "stretch",
263
+ text_alignment: TextAlignment = "left",
229
264
  ) -> DeltaGenerator:
230
265
  """Display text in title formatting.
231
266
 
@@ -269,6 +304,22 @@ class HeadingMixin:
269
304
  the parent container, the width of the element matches the width
270
305
  of the parent container.
271
306
 
307
+ text_alignment : "left", "center", "right", or "justify"
308
+ The horizontal alignment of the text within the element. This can
309
+ be one of the following:
310
+
311
+ - ``"left"`` (default): Text is aligned to the left edge.
312
+ - ``"center"``: Text is centered.
313
+ - ``"right"``: Text is aligned to the right edge.
314
+ - ``"justify"``: Text is justified (stretched to fill the available
315
+ width with the last line left-aligned).
316
+
317
+ .. note::
318
+ For text alignment to have a visible effect, the element's
319
+ width must be wider than its content. If you use
320
+ ``width="content"`` with short text, the alignment may not be
321
+ noticeable.
322
+
272
323
  Examples
273
324
  --------
274
325
  >>> import streamlit as st
@@ -282,12 +333,15 @@ class HeadingMixin:
282
333
 
283
334
  """
284
335
  validate_width(width, allow_content=True)
285
- layout_config = LayoutConfig(width=width)
336
+ layout_config = LayoutConfig(width=width, text_alignment=text_alignment)
286
337
 
287
338
  return self.dg._enqueue(
288
339
  "heading",
289
340
  HeadingMixin._create_heading_proto(
290
- tag=HeadingProtoTag.TITLE_TAG, body=body, anchor=anchor, help=help
341
+ tag=HeadingProtoTag.TITLE_TAG,
342
+ body=body,
343
+ anchor=anchor,
344
+ help=help,
291
345
  ),
292
346
  layout_config=layout_config,
293
347
  )
@@ -349,4 +403,5 @@ class HeadingMixin:
349
403
 
350
404
  if help:
351
405
  proto.help = help
406
+
352
407
  return proto
@@ -42,6 +42,7 @@ class HtmlMixin:
42
42
  body: str | Path | SupportsStr | SupportsReprHtml,
43
43
  *, # keyword-only arguments:
44
44
  width: Width = "stretch",
45
+ unsafe_allow_javascript: bool = False,
45
46
  ) -> DeltaGenerator:
46
47
  """Insert HTML into your app.
47
48
 
@@ -52,8 +53,10 @@ class HtmlMixin:
52
53
  loading external code can increase the risk of vulnerabilities in your
53
54
  app.
54
55
 
55
- ``st.html`` content is **not** iframed. Executing JavaScript is not
56
- supported at this time.
56
+ ``st.html`` content is **not** iframed. By default, JavaScript is
57
+ ignored. To execute JavaScript contained in your HTML, set
58
+ ``unsafe_allow_javascript=True``. Use this with caution and never pass
59
+ untrusted input.
57
60
 
58
61
  Parameters
59
62
  ----------
@@ -89,6 +92,12 @@ class HtmlMixin:
89
92
  the parent container, the width of the element matches the width
90
93
  of the parent container.
91
94
 
95
+ unsafe_allow_javascript : bool
96
+ Whether to execute JavaScript contained in your HTML. If this is
97
+ ``False`` (default), JavaScript is ignored. If this is ``True``,
98
+ JavaScript is executed. Use this with caution and never pass
99
+ untrusted input.
100
+
92
101
  Example
93
102
  -------
94
103
  >>> import streamlit as st
@@ -136,6 +145,8 @@ class HtmlMixin:
136
145
  html_proto.body = html_content
137
146
  return self._event_dg._enqueue("html", html_proto)
138
147
  # Otherwise, send the html to the main container as normal
148
+ # Only set the unsafe JS flag for non-style-only HTML content
149
+ html_proto.unsafe_allow_javascript = unsafe_allow_javascript
139
150
  html_proto.body = html_content
140
151
  return self.dg._enqueue("html", html_proto, layout_config=layout_config)
141
152
 
@@ -171,7 +171,7 @@ class ImageMixin:
171
171
 
172
172
  show_deprecation_warning(
173
173
  "The `use_column_width` parameter has been deprecated and will be removed "
174
- "in a future release. Please utilize the `use_container_width` parameter instead."
174
+ "in a future release. Please utilize the `width` parameter instead."
175
175
  )
176
176
  if use_column_width in {"auto", "never"} or use_column_width is False:
177
177
  width = "content"
@@ -62,7 +62,7 @@ class LayoutsMixin:
62
62
  *,
63
63
  border: bool | None = None,
64
64
  key: Key | None = None,
65
- width: WidthWithoutContent = "stretch",
65
+ width: Width = "stretch",
66
66
  height: Height = "content",
67
67
  horizontal: bool = False,
68
68
  horizontal_alignment: HorizontalAlignment = "left",
@@ -92,11 +92,13 @@ class LayoutsMixin:
92
92
  Additionally, if ``key`` is provided, it will be used as CSS
93
93
  class name prefixed with ``st-key-``.
94
94
 
95
- width : "stretch" or int
95
+ width : "stretch", "content", or int
96
96
  The width of the container. This can be one of the following:
97
97
 
98
98
  - ``"stretch"`` (default): The width of the container matches the
99
99
  width of the parent container.
100
+ - ``"content"``: The width of the container matches the width of
101
+ its content.
100
102
  - An integer specifying the width in pixels: The container has a
101
103
  fixed width. If the specified width is greater than the width of
102
104
  the parent container, the width of the container matches the width
@@ -199,7 +201,7 @@ class LayoutsMixin:
199
201
  >>>
200
202
  >>> st.write("This is outside the container")
201
203
 
202
- .. output ::
204
+ .. output::
203
205
  https://doc-container1.streamlit.app/
204
206
  height: 520px
205
207
 
@@ -218,7 +220,7 @@ class LayoutsMixin:
218
220
  >>>
219
221
  >>> container.write("This is inside too")
220
222
 
221
- .. output ::
223
+ .. output::
222
224
  https://doc-container2.streamlit.app/
223
225
  height: 300px
224
226
 
@@ -236,7 +238,7 @@ class LayoutsMixin:
236
238
  >>> tile = col.container(height=120)
237
239
  >>> tile.title(":balloon:")
238
240
 
239
- .. output ::
241
+ .. output::
240
242
  https://doc-container3.streamlit.app/
241
243
  height: 350px
242
244
 
@@ -252,7 +254,7 @@ class LayoutsMixin:
252
254
  >>> with st.container(height=300):
253
255
  >>> st.markdown(long_text)
254
256
 
255
- .. output ::
257
+ .. output::
256
258
  https://doc-container4.streamlit.app/
257
259
  height: 400px
258
260
 
@@ -268,7 +270,7 @@ class LayoutsMixin:
268
270
  >>> for card in range(3):
269
271
  >>> flex.button(f"Button {card + 1}")
270
272
 
271
- .. output ::
273
+ .. output::
272
274
  https://doc-container5.streamlit.app/
273
275
  height: 250px
274
276
 
@@ -298,7 +300,7 @@ class LayoutsMixin:
298
300
  block_proto.flex_container.justify = get_justify(vertical_alignment)
299
301
  block_proto.flex_container.align = get_align(horizontal_alignment)
300
302
 
301
- validate_width(width)
303
+ validate_width(width, allow_content=True)
302
304
  block_proto.width_config.CopyFrom(get_width_config(width))
303
305
 
304
306
  if isinstance(height, int) or border:
@@ -420,7 +422,7 @@ class LayoutsMixin:
420
422
  ... st.header("An owl")
421
423
  ... st.image("https://static.streamlit.io/examples/owl.jpg")
422
424
 
423
- .. output ::
425
+ .. output::
424
426
  https://doc-columns1.streamlit.app/
425
427
  height: 620px
426
428
 
@@ -441,7 +443,7 @@ class LayoutsMixin:
441
443
  >>> col2.subheader("A narrow column with the data")
442
444
  >>> col2.write(df)
443
445
 
444
- .. output ::
446
+ .. output::
445
447
  https://doc-columns2.streamlit.app/
446
448
  height: 550px
447
449
 
@@ -457,7 +459,7 @@ class LayoutsMixin:
457
459
  >>> middle.button("Click me", use_container_width=True)
458
460
  >>> right.checkbox("Check me")
459
461
 
460
- .. output ::
462
+ .. output::
461
463
  https://doc-columns-bottom-widgets.streamlit.app/
462
464
  height: 200px
463
465
 
@@ -476,7 +478,7 @@ class LayoutsMixin:
476
478
  >>> middle.image("https://static.streamlit.io/examples/dog.jpg")
477
479
  >>> right.image("https://static.streamlit.io/examples/owl.jpg")
478
480
 
479
- .. output ::
481
+ .. output::
480
482
  https://doc-columns-vertical-alignment.streamlit.app/
481
483
  height: 600px
482
484
 
@@ -493,7 +495,7 @@ class LayoutsMixin:
493
495
  >>> middle.markdown("Lorem ipsum " * 5)
494
496
  >>> right.markdown("Lorem ipsum ")
495
497
 
496
- .. output ::
498
+ .. output::
497
499
  https://doc-columns-borders.streamlit.app/
498
500
  height: 250px
499
501
 
@@ -640,7 +642,7 @@ class LayoutsMixin:
640
642
  ... st.header("An owl")
641
643
  ... st.image("https://static.streamlit.io/examples/owl.jpg", width=200)
642
644
 
643
- .. output ::
645
+ .. output::
644
646
  https://doc-tabs1.streamlit.app/
645
647
  height: 620px
646
648
 
@@ -661,7 +663,7 @@ class LayoutsMixin:
661
663
  >>> tab2.subheader("A tab with the data")
662
664
  >>> tab2.write(df)
663
665
 
664
- .. output ::
666
+ .. output::
665
667
  https://doc-tabs2.streamlit.app/
666
668
  height: 700px
667
669
 
@@ -686,7 +688,7 @@ class LayoutsMixin:
686
688
  >>> st.header("An owl")
687
689
  >>> st.image("https://static.streamlit.io/examples/owl.jpg", width=200)
688
690
 
689
- .. output ::
691
+ .. output::
690
692
  https://doc-tabs3.streamlit.app/
691
693
  height: 620px
692
694
 
@@ -791,6 +793,8 @@ class LayoutsMixin:
791
793
  <https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
792
794
  font library.
793
795
 
796
+ - ``"spinner"``: Displays a spinner as an icon.
797
+
794
798
  width : "stretch" or int
795
799
  The width of the expander container. This can be one of the following:
796
800
 
@@ -817,7 +821,7 @@ class LayoutsMixin:
817
821
  ... ''')
818
822
  ... st.image("https://static.streamlit.io/examples/dice.jpg")
819
823
 
820
- .. output ::
824
+ .. output::
821
825
  https://doc-expander.streamlit.app/
822
826
  height: 750px
823
827
 
@@ -835,7 +839,7 @@ class LayoutsMixin:
835
839
  ... ''')
836
840
  >>> expander.image("https://static.streamlit.io/examples/dice.jpg")
837
841
 
838
- .. output ::
842
+ .. output::
839
843
  https://doc-expander.streamlit.app/
840
844
  height: 750px
841
845
 
@@ -941,6 +945,8 @@ class LayoutsMixin:
941
945
  <https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
942
946
  font library.
943
947
 
948
+ - ``"spinner"``: Displays a spinner as an icon.
949
+
944
950
  disabled : bool
945
951
  An optional boolean that disables the popover button if set to
946
952
  ``True``. The default is ``False``.
@@ -993,7 +999,7 @@ class LayoutsMixin:
993
999
  >>>
994
1000
  >>> st.write("Your name:", name)
995
1001
 
996
- .. output ::
1002
+ .. output::
997
1003
  https://doc-popover.streamlit.app/
998
1004
  height: 400px
999
1005
 
@@ -1010,7 +1016,7 @@ class LayoutsMixin:
1010
1016
  >>> if blue:
1011
1017
  ... st.write(":blue[This is a blue item.]")
1012
1018
 
1013
- .. output ::
1019
+ .. output::
1014
1020
  https://doc-popover2.streamlit.app/
1015
1021
  height: 400px
1016
1022
 
@@ -1142,7 +1148,7 @@ class LayoutsMixin:
1142
1148
  >>>
1143
1149
  >>> st.button("Rerun")
1144
1150
 
1145
- .. output ::
1151
+ .. output::
1146
1152
  https://doc-status.streamlit.app/
1147
1153
  height: 300px
1148
1154
 
@@ -1165,7 +1171,7 @@ class LayoutsMixin:
1165
1171
  >>>
1166
1172
  >>> st.button("Rerun")
1167
1173
 
1168
- .. output ::
1174
+ .. output::
1169
1175
  https://doc-status-update.streamlit.app/
1170
1176
  height: 300px
1171
1177
 
@@ -46,6 +46,11 @@ if TYPE_CHECKING:
46
46
  VegaLiteType: TypeAlias = Literal["quantitative", "ordinal", "temporal", "nominal"]
47
47
  ChartStackType: TypeAlias = Literal["normalize", "center", "layered"]
48
48
 
49
+ # Threshold for applying hover event throttling on large datasets.
50
+ # For datasets with more points than this threshold, hover events are throttled
51
+ # to 16ms (~60fps) to improve performance.
52
+ _LARGE_DATASET_POINT_THRESHOLD: Final = 1000
53
+
49
54
 
50
55
  class PrepDataColumns(TypedDict):
51
56
  """Columns used for the prep_data step in Altair Arrow charts."""
@@ -286,37 +291,65 @@ def generate_chart(
286
291
  and is_altair_version_5_or_greater
287
292
  ):
288
293
  return _add_improved_hover_tooltips(
289
- chart, x_column, chart_width, chart_height
294
+ chart, x_column, chart_width, chart_height, len(df)
290
295
  ).interactive(), add_rows_metadata
291
296
 
292
297
  return chart.interactive(), add_rows_metadata
293
298
 
294
299
 
295
300
  def _add_improved_hover_tooltips(
296
- chart: alt.Chart, x_column: str, width: int | None, height: int | None
301
+ chart: alt.Chart,
302
+ x_column: str,
303
+ width: int | None,
304
+ height: int | None,
305
+ data_point_count: int,
297
306
  ) -> alt.LayerChart:
298
- """Adds improved hover tooltips to an existing line chart."""
307
+ """Adds improved hover tooltips to an existing line chart.
308
+
309
+ This implementation uses a three-layer approach for better performance:
310
+ 1. Base chart layer: The original line chart
311
+ 2. Detection layer: Invisible points for detecting the nearest point on hover
312
+ 3. Highlight layer: Only renders the selected point(s) using transform_filter
313
+
314
+ The filter-based approach is more efficient than using conditional opacity
315
+ because it only renders the selected point(s) rather than evaluating opacity
316
+ for every single data point on each hover event.
317
+ """
299
318
 
300
319
  import altair as alt
301
320
 
302
- # Create a selection that chooses the nearest point & selects based on x-value
321
+ # Throttle hover events for large datasets to 16ms (~60fps) to improve performance.
322
+ # For smaller datasets, use standard mousemove without throttling.
323
+ hover_event = (
324
+ "mousemove{16}"
325
+ if data_point_count > _LARGE_DATASET_POINT_THRESHOLD
326
+ else "mousemove"
327
+ )
328
+
329
+ # Create a selection that chooses the nearest point & selects based on x-value.
330
+ # Uses mouseleave instead of mouseout/pointerout for more reliable hover clearing
331
+ # (mouseout fires when moving over child elements like tooltips).
303
332
  nearest = alt.selection_point(
304
333
  nearest=True,
305
- on="pointerover",
334
+ on=hover_event,
306
335
  fields=[x_column],
307
336
  empty=False,
308
- clear="pointerout",
337
+ clear="mouseleave",
309
338
  )
310
339
 
311
- # Draw points on the line, and highlight based on selection
312
- points = (
313
- chart.mark_point(filled=True, size=65)
314
- .encode(opacity=alt.condition(nearest, alt.value(1), alt.value(0)))
315
- .add_params(nearest)
340
+ # Detection layer: Invisible points for detecting the nearest point.
341
+ # This layer is needed because selections must be attached to a mark.
342
+ detection_points = chart.mark_point(opacity=0).add_params(nearest)
343
+
344
+ # Highlight layer: Only renders the selected point(s) using transform_filter.
345
+ # This is more efficient than conditional opacity because it only renders
346
+ # the filtered data (typically 1-2 points) rather than all points.
347
+ highlighted_points = chart.mark_point(filled=True, size=65).transform_filter(
348
+ nearest
316
349
  )
317
350
 
318
351
  layer_chart = (
319
- alt.layer(chart, points)
352
+ alt.layer(chart, detection_points, highlighted_points)
320
353
  .configure_legend(symbolType="stroke")
321
354
  .properties(
322
355
  width=width or 0,
@@ -338,7 +371,7 @@ def prep_chart_data_for_add_rows(
338
371
  """
339
372
  import pandas as pd
340
373
 
341
- df = cast("pd.DataFrame", dataframe_util.convert_anything_to_pandas_df(data))
374
+ df = dataframe_util.convert_anything_to_pandas_df(data)
342
375
 
343
376
  # Make range indices start at last_index.
344
377
  if isinstance(df.index, pd.RangeIndex):
@@ -431,7 +464,7 @@ def _infer_vegalite_type(
431
464
 
432
465
 
433
466
  def _get_pandas_index_attr(
434
- data: pd.DataFrame | pd.Series,
467
+ data: pd.DataFrame | pd.Series[Any],
435
468
  attr: str,
436
469
  ) -> Any | None:
437
470
  return getattr(data.index, attr, None)
@@ -634,7 +667,7 @@ def _drop_unused_columns(df: pd.DataFrame, *column_names: str | None) -> pd.Data
634
667
  seen.add(x)
635
668
  keep.append(x)
636
669
 
637
- return df[keep]
670
+ return df[keep] # ty: ignore[invalid-return-type]
638
671
 
639
672
 
640
673
  def _maybe_convert_color_column_in_place(
@@ -651,7 +684,7 @@ def _maybe_convert_color_column_in_place(
651
684
  pass
652
685
  elif is_color_tuple_like(first_color_datum):
653
686
  # Tuples need to be converted to CSS-valid.
654
- df.loc[:, color_column] = df[color_column].map(to_css_color)
687
+ df.loc[:, color_column] = df[color_column].apply(to_css_color)
655
688
  else:
656
689
  # Other kinds of colors columns (i.e. pure numbers or nominal strings) shouldn't
657
690
  # be converted since they are treated by Vega-Lite as sequential or categorical
@@ -219,7 +219,7 @@ def _normalize_tuple(
219
219
  r = rgb_formatter(color_4tuple[0], color_4tuple)
220
220
  g = rgb_formatter(color_4tuple[1], color_4tuple)
221
221
  b = rgb_formatter(color_4tuple[2], color_4tuple)
222
- alpha = alpha_formatter(color_4tuple[3], color_4tuple)
222
+ alpha = alpha_formatter(color_4tuple[3], color_4tuple) # ty: ignore[index-out-of-bounds]
223
223
  return r, g, b, alpha
224
224
 
225
225
  raise StreamlitInvalidColorError(color)
@@ -18,7 +18,7 @@ import copy
18
18
  import json
19
19
  from collections.abc import Mapping
20
20
  from enum import Enum
21
- from typing import TYPE_CHECKING, Final, Literal, TypeAlias
21
+ from typing import TYPE_CHECKING, Any, Final, Literal, TypeAlias
22
22
 
23
23
  from streamlit.dataframe_util import DataFormat
24
24
  from streamlit.elements.lib.column_types import ColumnConfig, ColumnType
@@ -208,7 +208,7 @@ def _determine_data_kind_via_arrow(field: pa.Field) -> ColumnDataKind:
208
208
 
209
209
 
210
210
  def _determine_data_kind_via_pandas_dtype(
211
- column: Series | Index,
211
+ column: Series[Any] | Index[Any],
212
212
  ) -> ColumnDataKind:
213
213
  """Determine the data kind by using the pandas dtype.
214
214
 
@@ -262,7 +262,7 @@ def _determine_data_kind_via_pandas_dtype(
262
262
 
263
263
 
264
264
  def _determine_data_kind_via_inferred_type(
265
- column: Series | Index,
265
+ column: Series[Any] | Index[Any],
266
266
  ) -> ColumnDataKind:
267
267
  """Determine the data kind by inferring it from the underlying data.
268
268
 
@@ -331,7 +331,7 @@ def _determine_data_kind_via_inferred_type(
331
331
 
332
332
 
333
333
  def _determine_data_kind(
334
- column: Series | Index, field: pa.Field | None = None
334
+ column: Series[Any] | Index[Any], field: pa.Field | None = None
335
335
  ) -> ColumnDataKind:
336
336
  """Determine the data kind of a column.
337
337
 
@@ -395,7 +395,8 @@ def determine_dataframe_schema(
395
395
 
396
396
  # Add types for all columns:
397
397
  for i, column in enumerate(data_df.items()):
398
- column_name, column_data = column
398
+ column_name = str(column[0])
399
+ column_data = column[1]
399
400
  dataframe_schema[column_name] = _determine_data_kind(
400
401
  column_data, arrow_schema.field(i)
401
402
  )
@@ -21,12 +21,14 @@ from streamlit.errors import (
21
21
  StreamlitInvalidHeightError,
22
22
  StreamlitInvalidHorizontalAlignmentError,
23
23
  StreamlitInvalidSizeError,
24
+ StreamlitInvalidTextAlignmentError,
24
25
  StreamlitInvalidVerticalAlignmentError,
25
26
  StreamlitInvalidWidthError,
26
27
  )
27
28
  from streamlit.proto.Block_pb2 import Block
28
29
  from streamlit.proto.GapSize_pb2 import GapSize
29
30
  from streamlit.proto.HeightConfig_pb2 import HeightConfig
31
+ from streamlit.proto.TextAlignmentConfig_pb2 import TextAlignmentConfig
30
32
  from streamlit.proto.WidthConfig_pb2 import WidthConfig
31
33
 
32
34
  WidthWithoutContent: TypeAlias = int | Literal["stretch"]
@@ -37,6 +39,7 @@ SpaceSize: TypeAlias = int | Literal["stretch", "small", "medium", "large"]
37
39
  Gap: TypeAlias = Literal["small", "medium", "large"]
38
40
  HorizontalAlignment: TypeAlias = Literal["left", "center", "right", "distribute"]
39
41
  VerticalAlignment: TypeAlias = Literal["top", "center", "bottom", "distribute"]
42
+ TextAlignment: TypeAlias = Literal["left", "center", "right", "justify"]
40
43
 
41
44
  # Mapping of size literals to rem values for st.space
42
45
  # If changing these, also check streamlit/frontend/lib/src/theme/primitives/sizes.ts
@@ -52,6 +55,7 @@ SIZE_TO_REM_MAPPING = {
52
55
  class LayoutConfig:
53
56
  width: Width | SpaceSize | None = None
54
57
  height: Height | SpaceSize | None = None
58
+ text_alignment: TextAlignment | None = None
55
59
 
56
60
 
57
61
  def validate_width(width: Width, allow_content: bool = False) -> None:
@@ -210,6 +214,24 @@ def validate_vertical_alignment(vertical_alignment: VerticalAlignment) -> None:
210
214
  raise StreamlitInvalidVerticalAlignmentError(vertical_alignment, "st.container")
211
215
 
212
216
 
217
+ def validate_text_alignment(text_alignment: TextAlignment) -> None:
218
+ """Validate the text_alignment parameter.
219
+
220
+ Parameters
221
+ ----------
222
+ text_alignment : TextAlignment
223
+ The text alignment value to validate.
224
+
225
+ Raises
226
+ ------
227
+ StreamlitInvalidTextAlignmentError
228
+ If the text_alignment value is invalid.
229
+ """
230
+ valid_alignments = ["left", "center", "right", "justify"]
231
+ if text_alignment not in valid_alignments:
232
+ raise StreamlitInvalidTextAlignmentError(text_alignment)
233
+
234
+
213
235
  map_to_flex_terminology = {
214
236
  "left": "start",
215
237
  "center": "center",
@@ -249,3 +271,31 @@ def get_align(
249
271
  "Block.FlexContainer.Align.ValueType",
250
272
  getattr(Block.FlexContainer.Align, f"ALIGN_{align.upper()}"),
251
273
  )
274
+
275
+
276
+ def get_text_alignment_config(
277
+ text_alignment: TextAlignment,
278
+ ) -> TextAlignmentConfig:
279
+ """Convert text alignment string to proto config.
280
+
281
+ Parameters
282
+ ----------
283
+ text_alignment : TextAlignment
284
+ The text alignment value ("left", "center", "right", "justify").
285
+
286
+ Returns
287
+ -------
288
+ TextAlignmentConfig
289
+ Proto message with alignment set.
290
+ """
291
+
292
+ alignment_mapping = {
293
+ "left": TextAlignmentConfig.Alignment.LEFT,
294
+ "center": TextAlignmentConfig.Alignment.CENTER,
295
+ "right": TextAlignmentConfig.Alignment.RIGHT,
296
+ "justify": TextAlignmentConfig.Alignment.JUSTIFY,
297
+ }
298
+
299
+ config = TextAlignmentConfig()
300
+ config.alignment = alignment_mapping[text_alignment]
301
+ return config