From c10c451fdb6881e6e2b4cb1431682500f1ec440c Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 23 Jul 2025 09:41:34 -0500 Subject: [PATCH 1/8] Deprecate panel_well() in favor of card() --- shiny/express/ui/_cm_components.py | 9 +++------ shiny/ui/_bootstrap.py | 20 +++----------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/shiny/express/ui/_cm_components.py b/shiny/express/ui/_cm_components.py index b4b7f8e98..44d21a668 100644 --- a/shiny/express/ui/_cm_components.py +++ b/shiny/express/ui/_cm_components.py @@ -7,6 +7,7 @@ from htmltools import Tag, TagAttrs, TagAttrValue, TagChild, TagFunction, TagList from ... import ui +from ..._deprecated import warn_deprecated from ..._docstring import add_example, no_example from ...types import DEPRECATED, MISSING, MISSING_TYPE from ...ui._accordion import AccordionPanel @@ -1363,13 +1364,9 @@ def value_box( @no_example() def panel_well(**kwargs: TagAttrValue) -> RecallContextManager[Tag]: """ - Context manager for a well panel - - This function wraps :func:`~shiny.ui.panel_well`. - - A well panel is a simple container with a border and some padding. It's useful for - grouping related content together. + Deprecated. Use :func:`~shiny.express.ui.card` instead. """ + warn_deprecated("panel_well() is deprecated. Use shiny.express.ui.card() instead.") return RecallContextManager( ui.panel_well, kwargs=dict( diff --git a/shiny/ui/_bootstrap.py b/shiny/ui/_bootstrap.py index 1aacbb7f0..d269f7907 100644 --- a/shiny/ui/_bootstrap.py +++ b/shiny/ui/_bootstrap.py @@ -11,7 +11,6 @@ "help_text", ) - from typing import Literal, Optional from htmltools import ( @@ -27,6 +26,7 @@ tags, ) +from .._deprecated import warn_deprecated from .._docstring import add_example, no_example from ..module import current_namespace from ..types import MISSING, MISSING_TYPE @@ -112,23 +112,9 @@ def column( @no_example() def panel_well(*args: TagChild | TagAttrs, **kwargs: TagAttrValue) -> Tag: """ - Create a well panel. - - Creates a panel with a slightly inset border and gray background. Equivalent to - Bootstrap's ``well`` CSS class. - - Parameters - ---------- - *args - UI elements to include inside the panel. - **kwargs - Attributes to place on the panel tag. - - Returns - ------- - : - A UI element. + Deprecated. Use :func:`~shiny.ui.card` instead. """ + warn_deprecated("panel_well() is deprecated. Use shiny.ui.card() instead.") return div({"class": "well"}, *args, **kwargs) From 331a2c6c42d974b7344d13d2d1b8ac2e38ecc94d Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 23 Jul 2025 09:44:17 -0500 Subject: [PATCH 2/8] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1324948a..48539a136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fix missing session when trying to display an error duing bookmarking. (#1984) +### Deprecations + +* `ui.panel_well()` is deprecated in favor of `ui.card()`. (#2038) + ## [1.4.0] - 2025-04-08 From 60b6c54a79a73173815f88b47af5193adf43115e Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Thu, 24 Jul 2025 10:13:13 -0400 Subject: [PATCH 3/8] Remove deprecated function in docs --- docs/_quartodoc-core.yml | 1 - docs/_quartodoc-express.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/_quartodoc-core.yml b/docs/_quartodoc-core.yml index 57b6cf86f..0a8d2d9bf 100644 --- a/docs/_quartodoc-core.yml +++ b/docs/_quartodoc-core.yml @@ -91,7 +91,6 @@ quartodoc: - ui.panel_fixed - ui.panel_conditional - ui.panel_title - - ui.panel_well - title: Uploads & downloads desc: Allow users to upload and download files. contents: diff --git a/docs/_quartodoc-express.yml b/docs/_quartodoc-express.yml index 28d976969..548e3cbae 100644 --- a/docs/_quartodoc-express.yml +++ b/docs/_quartodoc-express.yml @@ -157,7 +157,6 @@ quartodoc: - express.ui.panel_absolute - express.ui.panel_fixed - express.ui.panel_title - - express.ui.panel_well - title: Uploads & downloads desc: Allow users to upload and download files. contents: From fd89a2c2e4602f80fef3840a9537e2ef67a2b212 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 25 Jul 2025 10:53:20 -0500 Subject: [PATCH 4/8] Migrate and modernize panel_well() examples --- examples/airmass/app.py | 48 +++---- examples/inputs-update/app.py | 128 ++++++++---------- shiny/api-examples/panel_absolute/app-core.py | 5 +- .../panel_absolute/app-express.py | 4 +- 4 files changed, 85 insertions(+), 100 deletions(-) diff --git a/examples/airmass/app.py b/examples/airmass/app.py index bfb83ef32..45ba21bc8 100644 --- a/examples/airmass/app.py +++ b/examples/airmass/app.py @@ -14,45 +14,37 @@ from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui -app_ui = ui.page_fixed( - ui.tags.h3("Air mass calculator"), - ui.div( +app_ui = ui.page_fillable( + ui.tags.h2("Air mass calculator", {"class": "lead display-6 text-center"}), + ui.layout_columns( ui.markdown( """This Shiny app uses [Astropy](https://www.astropy.org/) to calculate the - altitude (degrees above the horizon) and airmass (the amount of atmospheric - air along your line of sight to an object) of one or more astronomical - objects, over a given evening, at a given geographic location. - """ + altitude (degrees above the horizon) and airmass (the amount of atmospheric + air along your line of sight to an object) of one or more astronomical + objects, over a given evening, at a given geographic location. + """ ), - class_="mb-5", - ), - ui.row( - ui.column( - 8, + ui.card( ui.output_ui("timeinfo"), - ui.output_plot("plot", height="800px"), + ui.output_plot("plot", height="600px"), # For debugging # ui.output_table("table"), - class_="order-2 order-sm-1", ), - ui.column( - 4, - ui.panel_well( - ui.input_date("date", "Date"), - class_="pb-1 mb-3", - ), - ui.panel_well( + ui.TagList( + ui.card(ui.input_date("date", "Date"), fill=False), + ui.card( ui.input_text_area( - "objects", "Target object(s)", "M1, NGC35, PLX299", rows=3 + "objects", + "Target object(s)", + "M1, NGC35, PLX299", + rows=2, ), - class_="pb-1 mb-3", - ), - ui.panel_well( - location_ui("location"), - class_="mb-3", + fill=False, ), - class_="order-1 order-sm-2", + ui.card(location_ui("location")), ), + col_widths=[-2, 8, -2, -1, 7, 3, -1], + min_height="600px", ), ) diff --git a/examples/inputs-update/app.py b/examples/inputs-update/app.py index 092a1d2c6..bdc5e6917 100644 --- a/examples/inputs-update/app.py +++ b/examples/inputs-update/app.py @@ -4,78 +4,70 @@ app_ui = ui.page_fluid( ui.panel_title("Changing the values of inputs from the server"), - ui.row( - ui.column( - 4, - ui.panel_well( - ui.tags.h4("These inputs control the other inputs on the page"), - ui.input_text( - "control_label", "This controls some of the labels:", "LABEL TEXT" - ), - ui.input_slider( - "control_num", "This controls values:", min=1, max=20, value=15 - ), + ui.layout_columns( + ui.card( + ui.card_header("These inputs control the other inputs on the page"), + ui.input_text( + "control_label", "This controls some of the labels:", "LABEL TEXT" + ), + ui.input_slider( + "control_num", "This controls values:", min=1, max=20, value=15 ), ), - ui.column( - 4, - ui.panel_well( - ui.tags.h4("These inputs are controlled by the other inputs"), - ui.input_text("inText", "Text input:", value="start text"), - ui.input_numeric( - "inNumber", "Number input:", min=1, max=20, value=5, step=0.5 - ), - ui.input_numeric( - "inNumber2", "Number input 2:", min=1, max=20, value=5, step=0.5 - ), - ui.input_slider("inSlider", "Slider input:", min=1, max=20, value=15), - ui.input_slider( - "inSlider2", "Slider input 2:", min=1, max=20, value=(5, 15) - ), - ui.input_slider( - "inSlider3", "Slider input 3:", min=1, max=20, value=(5, 15) - ), - ui.input_date("inDate", "Date input:"), - ui.input_date_range("inDateRange", "Date range input:"), + ui.card( + ui.card_header("These inputs are controlled by the other inputs"), + ui.input_text("inText", "Text input:", value="start text"), + ui.input_numeric( + "inNumber", "Number input:", min=1, max=20, value=5, step=0.5 + ), + ui.input_numeric( + "inNumber2", "Number input 2:", min=1, max=20, value=5, step=0.5 + ), + ui.input_slider("inSlider", "Slider input:", min=1, max=20, value=15), + ui.input_slider( + "inSlider2", "Slider input 2:", min=1, max=20, value=(5, 15) + ), + ui.input_slider( + "inSlider3", "Slider input 3:", min=1, max=20, value=(5, 15) ), + ui.input_date("inDate", "Date input:"), + ui.input_date_range("inDateRange", "Date range input:"), ), - ui.column( - 4, - ui.panel_well( - ui.input_checkbox("inCheckbox", "Checkbox input", value=False), - ui.input_checkbox_group( - "inCheckboxGroup", - "Checkbox group input:", - { - "option1": "label 1", - "option2": "label 2", - }, - ), - ui.input_radio_buttons( - "inRadio", - "Radio buttons:", - { - "option1": "label 1", - "option2": "label 2", - }, - ), - ui.input_select( - "inSelect", - "Select input:", - { - "option1": "label 1", - "option2": "label 2", - }, - ), - ui.input_select( - "inSelect2", - "Select input 2:", - { - "option1": "label 1", - "option2": "label 2", - }, - multiple=True, - ), + ui.card( + ui.card_header("These inputs are updated by the server"), + ui.input_checkbox("inCheckbox", "Checkbox input", value=False), + ui.input_checkbox_group( + "inCheckboxGroup", + "Checkbox group input:", + { + "option1": "label 1", + "option2": "label 2", + }, + ), + ui.input_radio_buttons( + "inRadio", + "Radio buttons:", + { + "option1": "label 1", + "option2": "label 2", + }, + ), + ui.input_select( + "inSelect", + "Select input:", + { + "option1": "label 1", + "option2": "label 2", + }, + ), + ui.input_select( + "inSelect2", + "Select input 2:", + { + "option1": "label 1", + "option2": "label 2", + }, + multiple=True, ), ui.navset_tab( ui.nav_panel("panel1", ui.h2("This is the first panel.")), diff --git a/shiny/api-examples/panel_absolute/app-core.py b/shiny/api-examples/panel_absolute/app-core.py index 3340e5f93..4597dd1e3 100644 --- a/shiny/api-examples/panel_absolute/app-core.py +++ b/shiny/api-examples/panel_absolute/app-core.py @@ -3,8 +3,9 @@ app_ui = ui.page_fluid( ui.panel_title("A basic absolute panel example", "Demo"), ui.panel_absolute( - ui.panel_well( - "Drag me around!", ui.input_slider("n", "N", min=0, max=100, value=20) + ui.card( + ui.card_header("Drag me around!"), + ui.input_slider("n", "N", min=0, max=100, value=20), ), draggable=True, width="300px", diff --git a/shiny/api-examples/panel_absolute/app-express.py b/shiny/api-examples/panel_absolute/app-express.py index 8cf254ecb..3e6a37219 100644 --- a/shiny/api-examples/panel_absolute/app-express.py +++ b/shiny/api-examples/panel_absolute/app-express.py @@ -3,6 +3,6 @@ ui.h2("A basic absolute panel example") with ui.panel_absolute(draggable=True, width="300px", right="50px", top="25%"): - with ui.panel_well(): - "Drag me around!" + with ui.card(): + ui.card_header("Drag me around!") ui.input_slider("n", "N", min=0, max=100, value=20) From 4ff154abcd0693d75b2f6122df78817b45c70146 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 25 Jul 2025 14:54:53 -0500 Subject: [PATCH 5/8] ignore false-positive warning --- tests/playwright/examples/example_apps.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index 11333cade..79bf28f46 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -93,6 +93,8 @@ def get_apps(path: str) -> typing.List[str]: "RuntimeWarning: invalid value encountered in dot", # some groups didn't have enough data points to create a meaningful line "DatetimeIndex.format is deprecated and will be removed in a future version. Convert using index.astype(str) or index.map(formatter) instead.", # cufflinks package is using this method "Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`", # cufflinks package is using this method + # ignore known false-positive warning https://github.com/posit-dev/py-shiny/issues/1704 + "UserWarning: More column widths than children at breakpoint", ] app_allow_js_errors: typing.Dict[str, typing.List[str]] = { "examples/brownian": ["Failed to acquire camera feed:"], From 1205dff12232e66da08dad2060e96253afef0892 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Tue, 5 Aug 2025 06:16:37 -0700 Subject: [PATCH 6/8] Add 'warn(' to allowed external errors list Expanded the app_allow_external_errors list to include entries containing 'warn('. This helps filter out additional warning messages during test runs. --- tests/playwright/examples/example_apps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index 342b684ec..170f12885 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -92,6 +92,7 @@ def get_apps(path: str) -> typing.List[str]: "pd.option_context('mode.use_inf_as_na", # continutation of line above, "RuntimeWarning: invalid value encountered in dot", # some groups didn't have enough data points to create a meaningful line "UserWarning: More column widths than children at breakpoint", + "warn(", # Airmass example has this warning without any message ] app_allow_js_errors: typing.Dict[str, typing.List[str]] = { "examples/brownian": ["Failed to acquire camera feed:"], From 64f65f998d99bf262e8a9550a0be7eb4fa42f63c Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 5 Aug 2025 10:04:40 -0500 Subject: [PATCH 7/8] Close #1704: fix layout_columns() logic that warns about number of (positive) widths vs children --- shiny/ui/_layout_columns.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/shiny/ui/_layout_columns.py b/shiny/ui/_layout_columns.py index 927c08369..be20b0c23 100644 --- a/shiny/ui/_layout_columns.py +++ b/shiny/ui/_layout_columns.py @@ -186,31 +186,35 @@ def validate_col_width( x: Iterable[int] | int, n_kids: int, break_name: Breakpoint ) -> Iterable[int]: if isinstance(x, int): - y = [x] + widths = [x] else: - y = x + widths = list(x) - if not all(isinstance(i, int) for i in y): - raise ValueError( - f"Column values at breakpoint '{break_name}' must be integers. Values greater than 0 indicate width, and negative values indicate a column offset." - ) - - if any(i == 0 for i in y): - raise ValueError( - f"Column values at breakpoint '{break_name}' must be greater than 0 to indicate width, or negative to indicate a column offset." - ) + positive_widths: list[int] = [] + for w in widths: + if not isinstance(w, int): + raise TypeError( + f"Column widths at breakpoint '{break_name}' must be integers. Got {type(w).__name__}." + ) + if w == 0: + raise ValueError( + f"Column widths at breakpoint '{break_name}' must be greater than 0 to indicate width, or negative to indicate a column offset." + ) + if w > 0: + positive_widths.append(w) - if not any(b > 0 for b in y): + if not positive_widths: raise ValueError( - f"Column values at breakpoint '{break_name}' must include at least one positive integer width." + f"Column widths at breakpoint '{break_name}' must include at least one positive integer width." ) - if len(list(y)) > n_kids: + if len(positive_widths) > n_kids: warn( - f"More column widths than children at breakpoint '{break_name}', extra widths will be ignored." + f"More column widths than children at breakpoint '{break_name}', extra widths will be ignored.", + stacklevel=2, ) - return y + return widths def col_widths_attrs(col_widths: BreakpointsOptional[int] | None) -> TagAttrs: From c82a482000f9cfa642bb9ad96a43f0fb46d0a7c1 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 5 Aug 2025 10:06:29 -0500 Subject: [PATCH 8/8] No longer need to ignore warning; update changelog --- CHANGELOG.md | 2 ++ tests/playwright/examples/example_apps.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b47e599..ab2aa6f47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added module support for `session.clientdata` methods. This allows you to access client data values in Shiny modules without needing to namespace the keys explicitly. (#1978) +* Fixed false positive warning in `layout_columns()` about number of widths vs elements. (#1704) + ### Bug fixes * Fixed an issue with `ui.Chat()` sometimes wanting to scroll a parent element. (#1996) diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index 170f12885..c58229a6c 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -91,8 +91,6 @@ def get_apps(path: str) -> typing.List[str]: "FutureWarning: use_inf_as_na option is deprecated", "pd.option_context('mode.use_inf_as_na", # continutation of line above, "RuntimeWarning: invalid value encountered in dot", # some groups didn't have enough data points to create a meaningful line - "UserWarning: More column widths than children at breakpoint", - "warn(", # Airmass example has this warning without any message ] app_allow_js_errors: typing.Dict[str, typing.List[str]] = { "examples/brownian": ["Failed to acquire camera feed:"],