diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c9cc0ed5..181665ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,9 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `shiny.ui.navset_underline()` and `shiny.ui.navset_card_underline()` whose navigation container is similar to `shiny.ui.navset_tab()` and `shiny.ui.navset_card_tab()` respectively, but its active/focused navigation links are styled with an underline. (#772) * `shiny.ui.layout_column_wrap(width, *args)` was rearranged to `shiny.ui.layout_column_wrap(*args, width)`. Now, `width` will default to `200px` is no value is provided. (#772) * `shiny.ui.showcase_left_center()` and `shiny.ui.showcase_top_right()` no longer take two values for the `width` argument. Instead, they now take a single value (e.g., `width = "30%"`) representing the width of the showcase are in the value box. Furthermore, they've both gained `width_full_screen` arguments that determine the width of the showcase area when the value box is expanded to fill the screen. (#772) - - -* TODO-barret-API; `shiny.ui.panel_main()` and `shiny.ui.panel_sidebar()` are deprecated in favor of new API for `shiny.ui.layout_sidebar()`. Please use `shiny.ui.sidebar()` to construct a sidebar and supply it (along with the main content) to `shiny.ui.layout_sidebar(*args, **kwargs)`. (#680) +* `shiny.ui.panel_main()` and `shiny.ui.panel_sidebar()` are deprecated in favor of new API for `shiny.ui.layout_sidebar()`. Please use `shiny.ui.sidebar()` to construct a `sidebar=` and supply it to `shiny.ui.layout_sidebar(sidebar, *args, **kwargs)`. (#788) +* `shiny.experimental.ui.toggle_sidebar()` has been renamed to `shiny.ui.update_sidebar()`. It's `open` value now only supports `bool` values. (#788) #### API relocations @@ -46,18 +45,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The following methods have been moved from `shiny.experimental.ui` and integrated into `shiny.ui` (final locations under `shiny.ui` are displayed) (#680): * Sidebar - Sidebar layout or manipulation - * `sidebar()`, `page_sidebar()`, `toggle_sidebar()`, `layout_sidebar()`, `Sidebar` + * `sidebar()`, `page_sidebar()`, `update_sidebar()`, `layout_sidebar()`, `Sidebar` * Filling layout - Allow UI components to expand into the parent container and/or allow its content to expand - * `page_fillable()`, `fill.as_fillable_container()`, `fill.as_fill_item()`, `fill.is_fillable_container()`, `fill.is_fill_item()`, `fill.remove_all_fill()` + * `page_fillable()`, `fill.as_fillable_container()`, `fill.as_fill_item()`, `fill.remove_all_fill()` * `output_plot(fill=)`, `output_image(fill=)`, `output_ui(fill=, fillable=)` * CSS units - CSS units and padding * `css.as_css_unit()`, `css.as_css_padding()`, `css.CssUnit` * Tooltip - Hover-based context UI element - * `tooltip()`, `toggle_tooltip()`, `update_tooltip()` + * `tooltip()`, `update_tooltip()` * Popover - Click-based context UI element - * `popover()`, `toggle_popover()`, `update_popover()` + * `popover()`, `update_popover()` * Accordion - Vertically collapsible UI element - * `accordion()`, `accordion_panel()`, `accordion_panel_close()`, `accordion_panel_insert()`, `accordion_panel_open()`, `accordion_panel_remove()`, `accordion_panel_set()`, `update_accordion_panel()`, `Accordion`, `AccordionPanel` + * `accordion()`, `accordion_panel()`, `insert_accordion_panel()`, `remove_accordion_panel()`, `update_accordion()`, `update_accordion_panel()`, `Accordion`, `AccordionPanel` * Card - A general purpose container for grouping related UI elements together * `card()`, `card_header()`, `card_footer()`, `CardItem` * Valuebox - Opinionated container for displaying a value and title @@ -70,7 +69,6 @@ The following methods have been moved from `shiny.experimental.ui` and integrate * Layout - Layout of UI elements * `layout_column_wrap()` * Inputs - UI elements for user input - * `toggle_switch()` * `input_text_area(autoresize=)` If a ported method is called from `shiny.experimental.ui`, a deprecation warning will be displayed. @@ -84,10 +82,11 @@ Methods still under consideration in `shiny.experimental.ui`: #### API removals * `shiny.experimental.ui.FillingLayout` has been removed. (#481) +* `shiny.experimental.ui.toggle_switch()` has been made defunct. Please remove it from your code and use `shiny.ui.update_switch()` instead. (#772) * `shiny.experimental.ui.as_width_unit()` has been made defunct. Please remove it from your code. (#772) +* `shiny.experimental.ui`' `as_fill_carrier()`, `is_fill_carrier()`, `is_fillable_container()`, and `is_fill_item()` have been made defunct. Remove them from your code. (#680, #788) * Support for `min_height=`, `max_height=`, and `gap=` in `shiny.experimental.ui.as_fillable_container()` and `as_fill_item()` has been removed. (#481) -* `shiny.experimental.ui.TagCallable` has been deprecated. Its type is equivalent to `htmltools.TagFunction`. (#680) -* `shiny.experimental.ui.as_fill_carrier()` and `shiny.experimental.ui.is_fill_carrier()` have been deprecated. Please use `shiny.ui.fill.as_fill_item()` and `shiny.ui.fill.as_fillable_container()` or `shiny.ui.fill.is_fill_item()` and `shiny.ui.fill.is_fillable_container()` respectively in combination to achieve similar behavior. (#680) +* `shiny.experimental.ui.TagCallable` has been made defunct. Please use its type is equivalent to `htmltools.TagFunction`. (#680) ### Bug fixes diff --git a/docs/_quartodoc.yml b/docs/_quartodoc.yml index d6433cdf6..98f38e7af 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc.yml @@ -106,8 +106,8 @@ quartodoc: - ui.fill.as_fillable_container - ui.fill.as_fill_item - ui.fill.remove_all_fill - - ui.fill.is_fillable_container - - ui.fill.is_fill_item + # - ui.fill.is_fillable_container + # - ui.fill.is_fill_item - ui.css.as_css_unit - ui.css.as_css_padding - title: Update inputs @@ -140,13 +140,13 @@ quartodoc: - title: Update UI Layouts desc: "" contents: - - ui.toggle_sidebar - - ui.toggle_switch - - ui.toggle_tooltip - - ui.toggle_popover + - ui.update_sidebar - ui.update_tooltip - ui.update_popover + - ui.update_accordion - ui.update_accordion_panel + - ui.insert_accordion_panel + - ui.remove_accordion_panel - title: Rendering outputs desc: "UI (output_*()) and server (render)ing functions for generating content server-side." contents: diff --git a/shiny/api-examples/accordion_panel_close/app.py b/shiny/api-examples/accordion_panel_close/app.py deleted file mode 100644 index 9f396c7e0..000000000 --- a/shiny/api-examples/accordion_panel_close/app.py +++ /dev/null @@ -1,21 +0,0 @@ -from shiny import App, Inputs, Outputs, Session, reactive, ui - -items = [ - ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") - for letter in "ABCDE" -] - -app_ui = ui.page_fluid( - ui.input_action_button("close_acc", "Close Section C", class_="mt-3 mb-3"), - ui.accordion(*items, id="acc", multiple=True), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - @reactive.event(input.close_acc) - def _(): - ui.accordion_panel_close("acc", "Section C") - - -app = App(app_ui, server) diff --git a/shiny/api-examples/accordion_panel_open/app.py b/shiny/api-examples/accordion_panel_open/app.py deleted file mode 100644 index 4d92bc6c3..000000000 --- a/shiny/api-examples/accordion_panel_open/app.py +++ /dev/null @@ -1,21 +0,0 @@ -from shiny import App, Inputs, Outputs, Session, reactive, ui - -items = [ - ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") - for letter in "ABCDE" -] - -app_ui = ui.page_fluid( - ui.input_action_button("open_acc", "Open Section C", class_="mt-3 mb-3"), - ui.accordion(*items, id="acc", multiple=True), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - @reactive.event(input.open_acc) - def _(): - ui.accordion_panel_open("acc", "Section C") - - -app = App(app_ui, server) diff --git a/shiny/api-examples/accordion_panel_insert/app.py b/shiny/api-examples/insert_accordion_panel/app.py similarity index 90% rename from shiny/api-examples/accordion_panel_insert/app.py rename to shiny/api-examples/insert_accordion_panel/app.py index e5ac5d227..172460646 100644 --- a/shiny/api-examples/accordion_panel_insert/app.py +++ b/shiny/api-examples/insert_accordion_panel/app.py @@ -21,7 +21,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.add_panel) def _(): - ui.accordion_panel_insert("acc", make_panel(str(random.randint(0, 10000)))) + ui.insert_accordion_panel("acc", make_panel(str(random.randint(0, 10000)))) app = App(app_ui, server) diff --git a/shiny/api-examples/accordion_panel_remove/app.py b/shiny/api-examples/remove_accordion_panel/app.py similarity index 94% rename from shiny/api-examples/accordion_panel_remove/app.py rename to shiny/api-examples/remove_accordion_panel/app.py index cedca4f51..b04785498 100644 --- a/shiny/api-examples/accordion_panel_remove/app.py +++ b/shiny/api-examples/remove_accordion_panel/app.py @@ -37,7 +37,7 @@ def _(): return # Remove panel - ui.accordion_panel_remove("acc", f"Section { user_choices.pop() }") + ui.remove_accordion_panel("acc", f"Section { user_choices.pop() }") label = "No more panels to remove!" if len(user_choices) > 0: diff --git a/shiny/api-examples/toggle_sidebar/app.py b/shiny/api-examples/toggle_sidebar/app.py deleted file mode 100644 index 9df3da115..000000000 --- a/shiny/api-examples/toggle_sidebar/app.py +++ /dev/null @@ -1,26 +0,0 @@ -from shiny import App, Inputs, Outputs, Session, reactive, render, ui - -app_ui = ui.page_sidebar( - ui.sidebar("Sidebar content", id="sidebar"), - ui.input_action_button( - "toggle_sidebar", - label="Toggle sidebar", - width="fit-content", - ), - ui.output_text_verbatim("state"), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - @reactive.event(input.toggle_sidebar) - def _(): - ui.toggle_sidebar("sidebar") - - @output - @render.text - def state(): - return f"input.sidebar(): {input.sidebar()}" - - -app = App(app_ui, server=server) diff --git a/shiny/api-examples/toggle_switch/app.py b/shiny/api-examples/toggle_switch/app.py deleted file mode 100644 index 754082133..000000000 --- a/shiny/api-examples/toggle_switch/app.py +++ /dev/null @@ -1,26 +0,0 @@ -from shiny import App, Inputs, Outputs, Session, reactive, render, ui - -app_ui = ui.page_fluid( - ui.input_switch("switch_value", label="Switch"), - ui.input_action_button( - "toggle_btn", - label="Toggle the switch", - width="fit-content", - ), - ui.output_text_verbatim("state"), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - @reactive.event(input.toggle_btn) - def _(): - ui.toggle_switch("switch_value") - - @output - @render.text - def state(): - return f"input.switch(): {input.switch_value()}" - - -app = App(app_ui, server=server) diff --git a/shiny/api-examples/toggle_tooltip/app.py b/shiny/api-examples/toggle_tooltip/app.py deleted file mode 100644 index ce4ad6885..000000000 --- a/shiny/api-examples/toggle_tooltip/app.py +++ /dev/null @@ -1,43 +0,0 @@ -from shiny import App, Inputs, Outputs, Session, reactive, req, ui - -app_ui = ui.page_fluid( - ui.input_action_button("btn_show", "Show tooltip", class_="mt-3 me-3"), - ui.input_action_button("btn_close", "Close tooltip", class_="mt-3 me-3"), - ui.br(), - ui.input_action_button("btn_toggle", "Toggle tooltip", class_="mt-3 me-3"), - ui.br(), - ui.br(), - ui.tooltip( - ui.input_action_button("btn_w_tooltip", "A button w/ a tooltip", class_="mt-3"), - "A message", - id="tooltip_id", - ), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - def _(): - req(input.btn_show()) - - ui.toggle_tooltip("tooltip_id", show=True) - - @reactive.Effect - def _(): - req(input.btn_close()) - - ui.toggle_tooltip("tooltip_id", show=False) - - @reactive.Effect - def _(): - req(input.btn_toggle()) - - ui.toggle_tooltip("tooltip_id") - - @reactive.Effect - def _(): - req(input.btn_w_tooltip()) - ui.notification_show("Button clicked!", duration=3, type="message") - - -app = App(app_ui, server=server) diff --git a/shiny/api-examples/accordion_panel_set/app.py b/shiny/api-examples/update_accordion/app.py similarity index 88% rename from shiny/api-examples/accordion_panel_set/app.py rename to shiny/api-examples/update_accordion/app.py index 31ea62139..2ae14d64f 100644 --- a/shiny/api-examples/accordion_panel_set/app.py +++ b/shiny/api-examples/update_accordion/app.py @@ -16,7 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.set_acc) def _(): - ui.accordion_panel_set("acc", ["Section A", "Section C", "Section E"]) + ui.update_accordion("acc", show=["Section A", "Section C", "Section E"]) app = App(app_ui, server) diff --git a/shiny/api-examples/accordion_panel_update/app.py b/shiny/api-examples/update_accordion_panel/app.py similarity index 71% rename from shiny/api-examples/accordion_panel_update/app.py rename to shiny/api-examples/update_accordion_panel/app.py index f406b8890..f96fce999 100644 --- a/shiny/api-examples/accordion_panel_update/app.py +++ b/shiny/api-examples/update_accordion_panel/app.py @@ -12,7 +12,7 @@ def make_panel(letter: str) -> ui.AccordionPanel: items = [make_panel(letter) for letter in "ABCDE"] app_ui = ui.page_fluid( - ui.input_switch("update_panel", "Update Sections"), + ui.input_switch("update_panel", "Update (and open) Sections"), ui.accordion(*items, id="acc", multiple=True), ) @@ -22,13 +22,19 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.event(input.update_panel) def _(): txt = " (updated)" if input.update_panel() else "" + show = bool(input.update_panel() % 2 == 1) for letter in "ABCDE": ui.update_accordion_panel( "acc", f"sec_{letter}", f"Some{txt} narrative for section {letter}", title=f"Section {letter}{txt}", + # Open Accordion Panel to see updated contents + show=show, ) + next_show_txt = "close" if show else "open" + + ui.update_switch("update_panel", label=f"Update (and {next_show_txt}) Sections") app = App(app_ui, server) diff --git a/shiny/api-examples/toggle_popover/app.py b/shiny/api-examples/update_popover/app.py similarity index 72% rename from shiny/api-examples/toggle_popover/app.py rename to shiny/api-examples/update_popover/app.py index 4c4141833..f54f18de9 100644 --- a/shiny/api-examples/toggle_popover/app.py +++ b/shiny/api-examples/update_popover/app.py @@ -4,8 +4,6 @@ ui.input_action_button("btn_show", "Show popover", class_="mt-3 me-3"), ui.input_action_button("btn_close", "Close popover", class_="mt-3 me-3"), ui.br(), - ui.input_action_button("btn_toggle", "Toggle popover", class_="mt-3 me-3"), - ui.br(), ui.br(), ui.popover( ui.input_action_button("btn_w_popover", "A button w/ a popover", class_="mt-3"), @@ -20,19 +18,13 @@ def server(input: Inputs, output: Outputs, session: Session): def _(): req(input.btn_show()) - ui.toggle_popover("popover_id", show=True) + ui.update_popover("popover_id", show=True) @reactive.Effect def _(): req(input.btn_close()) - ui.toggle_popover("popover_id", show=False) - - @reactive.Effect - def _(): - req(input.btn_toggle()) - - ui.toggle_popover("popover_id") + ui.update_popover("popover_id", show=False) @reactive.Effect def _(): diff --git a/shiny/api-examples/update_sidebar/app.py b/shiny/api-examples/update_sidebar/app.py new file mode 100644 index 000000000..b0849155a --- /dev/null +++ b/shiny/api-examples/update_sidebar/app.py @@ -0,0 +1,31 @@ +from shiny import App, Inputs, Outputs, Session, reactive, render, ui + +app_ui = ui.page_sidebar( + ui.sidebar("Sidebar content", id="sidebar"), + ui.input_action_button("open_sidebar", label="Open sidebar", class_="me-3"), + ui.input_action_button("close_sidebar", label="Close sidebar", class_="me-3"), + ui.br(), + ui.br(), + ui.output_text_verbatim("state"), + fillable=False, +) + + +def server(input: Inputs, output: Outputs, session: Session): + @reactive.Effect + @reactive.event(input.open_sidebar) + def _(): + ui.update_sidebar("sidebar", show=True) + + @reactive.Effect + @reactive.event(input.close_sidebar) + def _(): + ui.update_sidebar("sidebar", show=False) + + @output + @render.text + def state(): + return f"input.sidebar(): {input.sidebar()}" + + +app = App(app_ui, server=server) diff --git a/shiny/api-examples/update_tooltip/app.py b/shiny/api-examples/update_tooltip/app.py index 786c73c41..aa81eee94 100644 --- a/shiny/api-examples/update_tooltip/app.py +++ b/shiny/api-examples/update_tooltip/app.py @@ -1,7 +1,12 @@ -from shiny import App, Inputs, Outputs, Session, reactive, ui +from shiny import App, Inputs, Outputs, Session, reactive, req, ui app_ui = ui.page_fluid( - ui.input_action_button("btn_update", "Update tooltip phrase", class_="mt-3 me-3"), + ui.input_action_button("btn_show", "Show tooltip", class_="mt-3 me-3"), + ui.input_action_button("btn_close", "Close tooltip", class_="mt-3 me-3"), + ui.br(), + ui.input_action_button( + "btn_update", "Update tooltip phrase (and show tooltip)", class_="mt-3 me-3" + ), ui.br(), ui.br(), ui.tooltip( @@ -15,8 +20,15 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect def _(): - # Immediately display tooltip - ui.toggle_tooltip("tooltip_id", show=True) + req(input.btn_show()) + + ui.update_tooltip("tooltip_id", show=True) + + @reactive.Effect + def _(): + req(input.btn_close()) + + ui.update_tooltip("tooltip_id", show=False) @reactive.Effect @reactive.event(input.btn_update) @@ -25,12 +37,11 @@ def _(): "A " + " ".join(["NEW" for _ in range(input.btn_update())]) + " message" ) - ui.update_tooltip("tooltip_id", content) - ui.toggle_tooltip("tooltip_id", show=True) + ui.update_tooltip("tooltip_id", content, show=True) @reactive.Effect - @reactive.event(input.btn_w_tooltip) def _(): + req(input.btn_w_tooltip()) ui.notification_show("Button clicked!", duration=3, type="message") diff --git a/shiny/experimental/ui/_deprecated.py b/shiny/experimental/ui/_deprecated.py index 677895978..9fb3f1601 100644 --- a/shiny/experimental/ui/_deprecated.py +++ b/shiny/experimental/ui/_deprecated.py @@ -13,23 +13,20 @@ ) from ..._deprecated import warn_deprecated -from ...session import Session +from ..._namespaces import resolve_id +from ..._utils import drop_none +from ...session import Session, require_active_session from ...types import MISSING, MISSING_TYPE from ...ui import AccordionPanel as MainAccordionPanel from ...ui import accordion as main_accordion from ...ui import accordion_panel as main_accordion_panel -from ...ui import accordion_panel_close as main_accordion_panel_close -from ...ui import accordion_panel_insert as main_accordion_panel_insert -from ...ui import accordion_panel_open as main_accordion_panel_open -from ...ui import accordion_panel_remove as main_accordion_panel_remove -from ...ui import accordion_panel_set as main_accordion_panel_set from ...ui import input_text_area as main_input_text_area +from ...ui import insert_accordion_panel as main_insert_accordion_panel from ...ui import popover as main_popover +from ...ui import remove_accordion_panel as main_remove_accordion_panel from ...ui import tags -from ...ui import toggle_popover as main_toggle_popover -from ...ui import toggle_switch as main_toggle_switch -from ...ui import toggle_tooltip as main_toggle_tooltip from ...ui import tooltip as main_tooltip +from ...ui import update_accordion as main_update_accordion from ...ui import update_accordion_panel as main_update_accordion_panel from ...ui import update_popover as main_update_popover from ...ui import update_tooltip as main_update_tooltip @@ -59,7 +56,7 @@ from ...ui._sidebar import panel_main as main_panel_main from ...ui._sidebar import panel_sidebar as main_panel_sidebar from ...ui._sidebar import sidebar as main_sidebar -from ...ui._sidebar import toggle_sidebar as main_toggle_sidebar +from ...ui._sidebar import update_sidebar as main_update_sidebar from ...ui._valuebox import ShowcaseLayout as MainShowcaseLayout from ...ui._valuebox import showcase_left_center as main_showcase_left_center from ...ui._valuebox import showcase_top_right as main_showcase_top_right @@ -69,9 +66,9 @@ from ...ui.css._css_unit import as_css_unit as main_as_css_unit from ...ui.fill import as_fill_item as main_as_fill_item from ...ui.fill import as_fillable_container as main_as_fillable_container -from ...ui.fill import is_fill_item as main_is_fill_item -from ...ui.fill import is_fillable_container as main_is_fillable_container from ...ui.fill import remove_all_fill as main_remove_all_fill +from ...ui.fill._fill import is_fill_item as main_is_fill_item +from ...ui.fill._fill import is_fillable_container as main_is_fillable_container __all__ = ( # Input Switch @@ -176,13 +173,22 @@ def toggle_switch( value: Optional[bool] = None, session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.toggle_switch()` instead.""" + """Defunct. Please do not use method.""" warn_deprecated( - "`shiny.experimental.ui.toggle_switch()` is deprecated. " + "`shiny.experimental.ui.toggle_switch()` is defunct. " "This method will be removed in a future version, " - "please use `shiny.ui.toggle_switch()` instead." + "please update your code accordingly." ) - return main_toggle_switch(id, value, session=session) + + if value is not None and not isinstance(value, bool): + raise TypeError("`value` must be `None` or a single boolean value.") + msg = drop_none({"id": resolve_id(id), "value": value}) + session = require_active_session(session) + + async def callback(): + await session.send_custom_message("bslib.toggle-input-binary", msg) + + session.on_flush(callback, once=True) ###################### @@ -350,13 +356,13 @@ def tooltip_toggle( show: Optional[bool] = None, session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.toggle_tooltip()`.""" + """Deprecated. Please use `shiny.ui.update_tooltip()`.""" warn_deprecated( "`shiny.experimental.ui.tooltip_toggle()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.toggle_tooltip()` instead." + "please use `shiny.ui.update_tooltip()` instead." ) - main_toggle_tooltip( + main_update_tooltip( id=id, show=show, session=session, @@ -369,13 +375,13 @@ def toggle_tooltip( show: Optional[bool] = None, session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.toggle_tooltip()` instead.""" + """Deprecated. Please use `shiny.ui.update_tooltip()` instead.""" warn_deprecated( "`shiny.experimental.ui.tooltip_toggle()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.toggle_tooltip()` instead." + "please use `shiny.ui.update_tooltip()` instead." ) - main_toggle_tooltip( + main_update_tooltip( id=id, show=show, session=session, @@ -455,7 +461,7 @@ def sidebar( def layout_sidebar( - sidebar: Sidebar | TagChild | TagAttrs, + sidebar: MainSidebar | TagChild | TagAttrs, *args: TagChild | TagAttrs, fillable: bool = True, fill: bool = True, @@ -475,6 +481,8 @@ def layout_sidebar( "This method will be removed in a future version, " "please use `shiny.ui.layout_sidebar()` instead." ) + if not isinstance(sidebar, MainSidebar): + sidebar = main_sidebar(sidebar) return main_layout_sidebar( sidebar, *args, @@ -497,15 +505,16 @@ def toggle_sidebar( open: Literal["toggle", "open", "closed", "always"] | bool | None = None, session: Session | None = None, ) -> None: - """Deprecated. Please use `shiny.ui.toggle_sidebar()` instead.""" + """Deprecated. Please use `shiny.ui.update_sidebar()` instead.""" warn_deprecated( "`shiny.experimental.ui.toggle_sidebar()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.toggle_sidebar()` instead." + "please use `shiny.ui.update_sidebar()` instead." ) - return main_toggle_sidebar( + open_val = (open is True) or (open == "open") + return main_update_sidebar( id, - open=open, + show=open_val, session=session, ) @@ -522,15 +531,17 @@ def sidebar_toggle( open: Literal["toggle", "open", "closed", "always"] | bool | None = None, session: Session | None = None, ) -> None: - """Deprecated. Please use `shiny.ui.toggle_sidebar()` instead of `sidebar_toggle()`.""" + """Deprecated. Please use `shiny.ui.update_sidebar()` instead of + `shiny.experimental.ui.sidebar_toggle()`.""" warn_deprecated( "`shiny.experimental.ui.sidebar_toggle()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.toggle_sidebar()` instead." + "please use `shiny.ui.update_sidebar()` instead." ) - main_toggle_sidebar( + open_val = (open is True) or (open == "open") + main_update_sidebar( id=id, - open=open, + show=open_val, session=session, ) @@ -672,13 +683,13 @@ def toggle_popover( show: Optional[bool] = None, session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.toggle_popover()` instead.""" + """Deprecated. Please use `shiny.ui.update_popover()` instead.""" warn_deprecated( - "`shiny.experimental.ui.toggle_popover()` is deprecated. " + "`shiny.experimental.ui.update_popover()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.toggle_popover()` instead." + "please use `shiny.ui.update_popover()` instead." ) - return main_toggle_popover(id, show, session=session) + return main_update_popover(id, show, session=session) # Deprecated 2023-09-12 @@ -769,13 +780,13 @@ def accordion_panel_set( values: bool | str | list[str], session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.accordion_panel_set()` instead.""" + """Deprecated. Please use `shiny.ui.update_accordion()` instead.""" warn_deprecated( "`shiny.experimental.ui.accordion_panel_set()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.accordion_panel_set()` instead." + "please use `shiny.ui.update_accordion()` instead." ) - return main_accordion_panel_set(id, values, session=session) + return main_update_accordion(id, show=values, session=session) # # Deprecated 2023-09-12 @@ -784,13 +795,22 @@ def accordion_panel_open( values: bool | str | list[str], session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.accordion_panel_open()` instead.""" + """Deprecated. Please use `shiny.ui.update_accordion_panel(id, value, show=True)` or `shiny.ui.update_accordion(id, show = True)` instead.""" warn_deprecated( "`shiny.experimental.ui.accordion_panel_open()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.accordion_panel_open()` instead." + "please use `shiny.ui.shiny.ui.update_accordion_panel(id, value, show=True)` or `shiny.ui.update_accordion(id, show = True)` instead." ) - return main_accordion_panel_open(id, values, session=session) + + if isinstance(values, bool): + main_update_accordion(id, show=True, session=session) + return + + if not isinstance(values, list): + values = [values] + + for value in values: + main_update_accordion_panel(id, value, show=True, session=session) # # Deprecated 2023-09-12 @@ -799,13 +819,21 @@ def accordion_panel_close( values: bool | str | list[str], session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.accordion_panel_close()` instead.""" + """Deprecated. Please use `shiny.ui.update_accordion_panel(id, value, show=False)` or `shiny.ui.update_accordion(id, show = False)` instead.""" warn_deprecated( "`shiny.experimental.ui.accordion_panel_close()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.accordion_panel_close()` instead." + "please use `shiny.ui.update_accordion_panel(id, value, show=False)` or `shiny.ui.update_accordion(id, show = False)` instead." ) - return main_accordion_panel_close(id, values, session=session) + if isinstance(values, bool): + main_update_accordion(id, show=False, session=session) + return + + if not isinstance(values, list): + values = [values] + + for value in values: + main_update_accordion_panel(id, value, show=False, session=session) # # Deprecated 2023-09-12 @@ -816,13 +844,13 @@ def accordion_panel_insert( position: Literal["after", "before"] = "after", session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.accordion_panel_insert()` instead.""" + """Deprecated. Please use `shiny.ui.insert_accordion_panel()` instead.""" warn_deprecated( "`shiny.experimental.ui.accordion_panel_insert()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.accordion_panel_insert()` instead." + "please use `shiny.ui.insert_accordion_panel()` instead." ) - return main_accordion_panel_insert( + return main_insert_accordion_panel( id, panel, target=target, @@ -837,13 +865,13 @@ def accordion_panel_remove( target: str | list[str], session: Optional[Session] = None, ) -> None: - """Deprecated. Please use `shiny.ui.accordion_panel_remove()` instead.""" + """Deprecated. Please use `shiny.ui.remove_accordion_panel()` instead.""" warn_deprecated( "`shiny.experimental.ui.accordion_panel_remove()` is deprecated. " "This method will be removed in a future version, " - "please use `shiny.ui.accordion_panel_remove()` instead." + "please use `shiny.ui.remove_accordion_panel()` instead." ) - return main_accordion_panel_remove( + return main_remove_accordion_panel( id, target=target, session=session, @@ -976,31 +1004,31 @@ def remove_all_fill(tag: TagT) -> TagT: def is_fill_carrier(tag: Tag) -> bool: - """Deprecated. Please use a combination of `shiny.ui.fill.is_fillable_container()` and `shiny.ui.fill.is_fill_item()` instead.""" + """Defunct. Please do not use method.""" warn_deprecated( - "`shiny.experimental.ui.is_fill_carrier()` is deprecated. " + "`shiny.experimental.ui.is_fill_carrier()` is defunct. " "This method will be removed in a future version, " - "please use a combination of `shiny.ui.fill.is_fillable_container()` and `shiny.ui.fill.is_fill_item()` instead." + "please update your code accordingly." ) return main_is_fill_item(main_is_fillable_container(tag)) def is_fillable_container(tag: TagChild) -> bool: - """Deprecated. Please use `shiny.ui.fill.is_fillable_container()` instead.""" + """Defunct. Please do not use method.""" warn_deprecated( - "`shiny.experimental.ui.is_fillable_container()` is deprecated. " + "`shiny.experimental.ui.is_fillable_container()` is defunct. " "This method will be removed in a future version, " - "please use `shiny.ui.fill.is_fillable_container()` instead." + "please update your code accordingly." ) return main_is_fillable_container(tag) def is_fill_item(tag: TagChild) -> bool: - """Deprecated. Please use `shiny.ui.fill.is_fill_item()` instead.""" + """Defunct. Please do not use method.""" warn_deprecated( - "`shiny.experimental.ui.is_fill_item()` is deprecated. " + "`shiny.experimental.ui.is_fill_item()` is defunct. " "This method will be removed in a future version, " - "please use `shiny.ui.fill.is_fill_item()` instead." + "please update your code accordingly." ) return main_is_fill_item(tag) @@ -1093,7 +1121,7 @@ def value_box( showcase=showcase, showcase_layout=showcase_layout_val, full_screen=full_screen, - theme_color=theme_color, + theme=theme_color, height=height, max_height=max_height, fill=fill, @@ -1387,6 +1415,8 @@ def page_sidebar( "This method will be removed in a future version, " "please use `shiny.ui.page_sidebar()` instead." ) + if not isinstance(sidebar, MainSidebar): + sidebar = main_sidebar(sidebar) return main_page_sidebar( sidebar, *args, diff --git a/shiny/ui/__init__.py b/shiny/ui/__init__.py index b32be1235..28f77e2e6 100644 --- a/shiny/ui/__init__.py +++ b/shiny/ui/__init__.py @@ -17,7 +17,7 @@ Sidebar, sidebar, layout_sidebar, - toggle_sidebar, + update_sidebar, panel_sidebar, panel_main, ) @@ -40,11 +40,9 @@ AccordionPanel, accordion, accordion_panel, - accordion_panel_open, - accordion_panel_close, - accordion_panel_insert, - accordion_panel_remove, - accordion_panel_set, + insert_accordion_panel, + remove_accordion_panel, + update_accordion, update_accordion_panel, ) @@ -126,7 +124,6 @@ from .dataframe import output_data_frame -from ._input_toggle import toggle_switch, toggle_tooltip, toggle_popover from ._popover import popover from ._valuebox import ( value_box, @@ -183,7 +180,7 @@ "Sidebar", "sidebar", "layout_sidebar", - "toggle_sidebar", + "update_sidebar", "panel_sidebar", "panel_main", # _layout @@ -197,11 +194,9 @@ "AccordionPanel", "accordion", "accordion_panel", - "accordion_panel_open", - "accordion_panel_close", - "accordion_panel_insert", - "accordion_panel_remove", - "accordion_panel_set", + "insert_accordion_panel", + "remove_accordion_panel", + "update_accordion", "update_accordion_panel", # _download_button "download_button", @@ -305,10 +300,6 @@ "page_fluid", "page_fixed", "page_bootstrap", - # _input_toggle - "toggle_switch", - "toggle_tooltip", - "toggle_popover", # _popover "popover", # _valuebox diff --git a/shiny/ui/_accordion.py b/shiny/ui/_accordion.py index 81eb35e95..8e1ddf00b 100644 --- a/shiny/ui/_accordion.py +++ b/shiny/ui/_accordion.py @@ -5,6 +5,7 @@ from htmltools import Tag, TagAttrs, TagAttrValue, TagChild, css, tags +from .._docstring import add_example from .._namespaces import resolve_id_or_none from .._utils import drop_none from ..session import Session, require_active_session @@ -16,13 +17,10 @@ __all__ = ( "accordion", "accordion_panel", - "accordion_panel_close", # TODO-barret-API: rename to `update_accordion_panel(open=False)`? - "accordion_panel_open", # TODO-barret-API: rename to `update_accordion_panel(open=True)`? - "accordion_panel_insert", - "accordion_panel_remove", - "accordion_panel_set", # TODO-barret-API: rename to `update_accordion(selected=)` - "update_accordion_panel", # TODO-barret-API: rename to `update_accordion()`? - # TODO-barret-API: Add `toggle_accordion(values=list[str] | None)`? - Toggles all accordion panels if `values=None` or toggle the specified panels + "insert_accordion_panel", + "remove_accordion_panel", + "update_accordion", + "update_accordion_panel", ) @@ -52,11 +50,9 @@ class AccordionPanel: See Also -------- * :func:`~shiny.ui.accordion` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` + * :func:`~shiny.ui.update_accordion` + * :func:`~shiny.ui.insert_accordion_panel` + * :func:`~shiny.ui.remove_accordion_panel` * :func:`~shiny.ui.update_accordion_panel` """ @@ -164,7 +160,7 @@ def tagify(self) -> Tag: return self.resolve().tagify() -# TODO-maindocs; @add_example() +@add_example() def accordion( *args: AccordionPanel | TagAttrs, id: Optional[str] = None, @@ -220,11 +216,9 @@ def accordion( See Also -------- * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` + * :func:`~shiny.ui.update_accordion` + * :func:`~shiny.ui.insert_accordion_panel` + * :func:`~shiny.ui.remove_accordion_panel` * :func:`~shiny.ui.update_accordion_panel` """ @@ -287,7 +281,7 @@ def accordion( return tag -# TODO-maindocs; @add_example() +@add_example() def accordion_panel( title: TagChild, *args: TagChild | TagAttrs, @@ -326,11 +320,9 @@ def accordion_panel( See Also -------- * :func:`~shiny.ui.accordion` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` + * :func:`~shiny.ui.update_accordion` + * :func:`~shiny.ui.insert_accordion_panel` + * :func:`~shiny.ui.remove_accordion_panel` * :func:`~shiny.ui.update_accordion_panel` """ @@ -388,10 +380,11 @@ def _accordion_panel_action( ) -# TODO-maindocs; @add_example() -def accordion_panel_set( +@add_example() +def update_accordion( id: str, - values: bool | str | list[str], + *, + show: bool | str | list[str], session: Optional[Session] = None, ) -> None: """ @@ -405,7 +398,7 @@ def accordion_panel_set( ---------- id A string that matches an existing :func:`~shiny.ui.accordion`'s `id`. - values + show either a string or list of strings (used to identify particular :func:`~shiny.ui.accordion_panel`(s) by their `value`) or a `bool` to set the state of all panels. @@ -420,91 +413,19 @@ def accordion_panel_set( -------- * :func:`~shiny.ui.accordion` * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` + * :func:`~shiny.ui.insert_accordion_panel` + * :func:`~shiny.ui.remove_accordion_panel` * :func:`~shiny.ui.update_accordion_panel` """ - _accordion_panel_action(id=id, method="set", values=values, session=session) - - -# TODO-maindocs; @add_example() -def accordion_panel_open( - id: str, - values: bool | str | list[str], - session: Optional[Session] = None, -) -> None: - """ - Open a set of :func:`~shiny.ui.accordion_panel`s. - - Parameters - ---------- - id - A string that matches an existing :func:`~shiny.ui.accordion`'s `id`. - values - either a string or list of strings (used to identify particular - :func:`~shiny.ui.accordion_panel`(s) by their `value`) or a `bool` to set the state of all - panels. - session - A shiny session object (the default should almost always be used). - - References - ---------- - [Bootstrap Accordion](https://getbootstrap.com/docs/5.3/components/accordion/) - - See Also - -------- - * :func:`~shiny.ui.accordion` - * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` - * :func:`~shiny.ui.update_accordion_panel` - """ - _accordion_panel_action(id=id, method="open", values=values, session=session) - - -# TODO-maindocs; @add_example() -def accordion_panel_close( - id: str, - values: bool | str | list[str], - session: Optional[Session] = None, -) -> None: - """ - Close a set of accordion panels in an :func:`~shiny.ui.accordion`. - - Parameters - ---------- - id - A string that matches an existing :func:`~shiny.ui.accordion`'s `id`. - values - either a string or list of strings (used to identify particular - :func:`~shiny.ui.accordion_panel`(s) by their `value`) or a `bool` to set the state of all - panels. - session - A shiny session object (the default should almost always be used). - - References - ---------- - [Bootstrap Accordion](https://getbootstrap.com/docs/5.3/components/accordion/) - - See Also - -------- - * :func:`~shiny.ui.accordion` - * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` - * :func:`~shiny.ui.update_accordion_panel` - """ - _accordion_panel_action(id=id, method="close", values=values, session=session) + if show is False: + show_val = [] + else: + show_val = show + _accordion_panel_action(id=id, method="set", values=show_val, session=session) -# TODO-maindocs; @add_example() -def accordion_panel_insert( +@add_example() +def insert_accordion_panel( id: str, panel: AccordionPanel, target: Optional[str] = None, @@ -537,10 +458,8 @@ def accordion_panel_insert( -------- * :func:`~shiny.ui.accordion` * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_remove` + * :func:`~shiny.ui.update_accordion` + * :func:`~shiny.ui.remove_accordion_panel` * :func:`~shiny.ui.update_accordion_panel` """ @@ -557,8 +476,8 @@ def accordion_panel_insert( ) -# TODO-maindocs; @add_example() -def accordion_panel_remove( +@add_example() +def remove_accordion_panel( id: str, target: str | list[str], session: Optional[Session] = None, @@ -583,10 +502,8 @@ def accordion_panel_remove( -------- * :func:`~shiny.ui.accordion` * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` + * :func:`~shiny.ui.update_accordion` + * :func:`~shiny.ui.insert_accordion_panel` * :func:`~shiny.ui.update_accordion_panel` """ if not isinstance(target, list): @@ -611,7 +528,7 @@ def _missing_none_x(x: T | None | MISSING_TYPE) -> T | Literal[""] | None: return x -# TODO-maindocs; @add_example() +@add_example() def update_accordion_panel( id: str, target: str, @@ -619,6 +536,7 @@ def update_accordion_panel( title: TagChild | None | MISSING_TYPE = MISSING, value: str | None | MISSING_TYPE = MISSING, icon: TagChild | None | MISSING_TYPE = MISSING, + show: Optional[bool] = None, session: Optional[Session] = None, ) -> None: """ @@ -653,15 +571,23 @@ def update_accordion_panel( -------- * :func:`~shiny.ui.accordion` * :func:`~shiny.ui.accordion_panel` - * :func:`~shiny.ui.accordion_panel_set` - * :func:`~shiny.ui.accordion_panel_open` - * :func:`~shiny.ui.accordion_panel_close` - * :func:`~shiny.ui.accordion_panel_insert` - * :func:`~shiny.ui.accordion_panel_remove` + * :func:`~shiny.ui.update_accordion` + * :func:`~shiny.ui.insert_accordion_panel` + * :func:`~shiny.ui.remove_accordion_panel` """ session = require_active_session(session) + # If `show` is given, then we need to open/close the targeted panel + # Perform before changing `value` at the same time. + if show is not None: + _accordion_panel_action( + id=id, + method="open" if bool(show) else "close", + values=[target], + session=session, + ) + title = _missing_none_x(title) value = _missing_none_x(value) icon = _missing_none_x(icon) diff --git a/shiny/ui/_card.py b/shiny/ui/_card.py index bfcf6aa26..aeaba298a 100644 --- a/shiny/ui/_card.py +++ b/shiny/ui/_card.py @@ -120,7 +120,7 @@ def _card_impl( Experimental implements the parameter `wrapper=`. Main does not as `card_body()` is not in `main`. """ - # TODO-barret-API: Should card_body() ever exist in main? + # TODO-question Should card_body() ever exist in main? # Barret: Yes? Need to customize fill/fillable content (ex: Fixed height for item B where content = A, B, C; fill = TRUE, fillable = TRUE). if isinstance(wrapper, MISSING_TYPE): wrapper = card_body diff --git a/shiny/ui/_input_check_radio.py b/shiny/ui/_input_check_radio.py index 86beed2a0..d00566364 100644 --- a/shiny/ui/_input_check_radio.py +++ b/shiny/ui/_input_check_radio.py @@ -148,10 +148,16 @@ def _bslib_input_checkbox( id=resolve_id(id), class_="form-check-input", type="checkbox", + role="switch", checked="checked" if value else None, ), " ", - tags.label(label, class_="form-check-label", for_=resolve_id(id)), + tags.label( + # Must be wrapped in `span` for update_switch(label=) method to work + tags.span(label), + class_="form-check-label", + for_=resolve_id(id), + ), class_=class_, ), components_dependency(), diff --git a/shiny/ui/_input_toggle.py b/shiny/ui/_input_toggle.py deleted file mode 100644 index e7318f3a7..000000000 --- a/shiny/ui/_input_toggle.py +++ /dev/null @@ -1,114 +0,0 @@ -from __future__ import annotations - -from typing import Literal, Optional - -from .._docstring import add_example -from .._utils import drop_none -from ..module import resolve_id -from ..session import Session, require_active_session -from ._utils import _session_on_flush_send_msg - - -@add_example() -# TODO-barret-API; Should toggle methods exist? You should know (or can retrieve) the current state and where you are wanting to go. -def toggle_popover( # TODO-barret-API; Move into update popover method - id: str, - show: Optional[bool] = None, - session: Optional[Session] = None, -) -> None: - """ - Programmatically show/hide a popover. - - Parameters - ---------- - id - The id of the popover DOM element to update. - show - Whether to show (`True`) or hide (`False`) the popover. The default - (`None`) will show if currently hidden and hide if currently shown. - Note that a popover will not be shown if the trigger is not visible - (e.g., it is hidden behind a tab). - session - A Shiny session object (the default should almost always be used). - - See Also - -------- - * :func:`~shiny.ui.popover` - * :func:`~shiny.ui.update_popover` - """ - session = require_active_session(session) - - _session_on_flush_send_msg( - id, - session, - { - "method": "toggle", - "value": _normalize_show_value(show), - }, - ) - - -@add_example() -# TODO-barret-API; Should toggle methods exist? You should know (or can retrieve) the current state and where you are wanting to go. -def toggle_tooltip( # TODO-barret-API; Move into update tooltip method - id: str, show: Optional[bool] = None, session: Optional[Session] = None -) -> None: - """ - Programmatically show/hide a tooltip - - Parameters - ---------- - id - A character string that matches an existing tooltip id. - show - Whether to show (`True`) or hide (`False`) the tooltip. The default (`None`) - will show if currently hidden and hide if currently shown. Note that a tooltip - will not be shown if the trigger is not visible (e.g., it's hidden behind a - tab). - session - A Shiny session object (the default should almost always be used). - """ - _session_on_flush_send_msg( - id, - session, - { - "method": "toggle", - "value": _normalize_show_value(show), - }, - ) - - -def _normalize_show_value(show: bool | None) -> Literal["toggle", "show", "hide"]: - if show is None: - return "toggle" - return "show" if show else "hide" - - -@add_example() -# TODO-barret-API; Should toggle methods exist? You should know (or can retrieve) the current state and where you are wanting to go. -def toggle_switch( # TODO-barret-API; Move into `update_switch()` method - id: str, value: Optional[bool] = None, session: Optional[Session] = None -): - """ - Toggle a switch input. - - Parameters - ---------- - id - The id of the switch input. - value - The new value of the switch input. If `NULL`, the value will be toggled. - session - The session object passed to `server()`. - """ - - if value is not None and not isinstance(value, bool): - raise TypeError("`value` must be `None` or a single boolean value.") - - msg = drop_none({"id": resolve_id(id), "value": value}) - session = require_active_session(session) - - async def callback(): - await session.send_custom_message("bslib.toggle-input-binary", msg) - - session.on_flush(callback, once=True) diff --git a/shiny/ui/_input_update.py b/shiny/ui/_input_update.py index 5351725b8..22a185d9c 100644 --- a/shiny/ui/_input_update.py +++ b/shiny/ui/_input_update.py @@ -21,7 +21,7 @@ import json import re from datetime import date -from typing import Literal, Mapping, Optional +from typing import Literal, Mapping, Optional, overload from htmltools import TagChild, TagList from starlette.requests import Request @@ -902,7 +902,12 @@ def update_navs( # tooltips.py # ----------------------------------------------------------------------------- @add_example() -def update_tooltip(id: str, *args: TagChild, session: Optional[Session] = None) -> None: +def update_tooltip( + id: str, + *args: TagChild, + show: Optional[bool] = None, + session: Optional[Session] = None, +) -> None: """ Update tooltip contents @@ -912,9 +917,12 @@ def update_tooltip(id: str, *args: TagChild, session: Optional[Session] = None) A character string that matches an existing tooltip id. *args Contents to the tooltip's body. + show + Opens (`True`) or closes (`False`) the tooltip. session A Shiny session object (the default should almost always be used). """ + _session_on_flush_send_msg( id, session, @@ -927,6 +935,15 @@ def update_tooltip(id: str, *args: TagChild, session: Optional[Session] = None) } ), ) + if show is not None: + _session_on_flush_send_msg( + id, + session, + { + "method": "toggle", + "value": _normalize_show_value(show), + }, + ) # ----------------------------------------------------------------------------- @@ -939,6 +956,7 @@ def update_popover( id: str, *args: TagChild, title: Optional[TagChild] = None, + show: Optional[bool] = None, session: Optional[Session] = None, ) -> None: """ @@ -952,26 +970,53 @@ def update_popover( The new contents of the popover. title The new title of the popover. + show + Opens (`True`) or closes (`False) the popover. session A Shiny session object (the default should almost always be used). See Also -------- * :func:`~shiny.ui.popover` - * :func:`~shiny.ui.toggle_popover` """ session = require_active_session(session) - _session_on_flush_send_msg( - id, - session, - drop_none( + if title is not None or len(args) > 0: + _session_on_flush_send_msg( + id, + session, + drop_none( + { + "method": "update", + "content": session._process_ui(TagList(*args)) + if len(args) > 0 + else None, + "header": session._process_ui(title) if title is not None else None, + }, + ), + ) + if show is not None: + _session_on_flush_send_msg( + id, + session, { - "method": "update", - "content": session._process_ui(TagList(*args)) - if len(args) > 0 - else None, - "header": session._process_ui(title) if title is not None else None, + "method": "toggle", + "value": _normalize_show_value(show), }, - ), - ) + ) + + +@overload +def _normalize_show_value(show: None) -> Literal["toggle"]: + ... + + +@overload +def _normalize_show_value(show: bool) -> Literal["show", "hide"]: + ... + + +def _normalize_show_value(show: bool | None) -> Literal["toggle", "show", "hide"]: + if show is None: + return "toggle" + return "show" if show else "hide" diff --git a/shiny/ui/_layout.py b/shiny/ui/_layout.py index 73cec22d8..57f422ec4 100644 --- a/shiny/ui/_layout.py +++ b/shiny/ui/_layout.py @@ -14,7 +14,6 @@ from .fill import as_fill_item, as_fillable_container -# TODO-barret-API; Move `width` to after `args` def layout_column_wrap( *args: TagChild | TagAttrs, width: CssUnit | None | MISSING_TYPE = MISSING, diff --git a/shiny/ui/_navs.py b/shiny/ui/_navs.py index 19b282276..f57338d76 100644 --- a/shiny/ui/_navs.py +++ b/shiny/ui/_navs.py @@ -683,9 +683,7 @@ def navset_card_tab( id: Optional[str] = None, selected: Optional[str] = None, title: Optional[TagChild] = None, - sidebar: Optional[ - Sidebar - ] = None, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` be of type `NavSetArg | Sidebar` and have the sidebar retrieved from within the args? + sidebar: Optional[Sidebar] = None, header: TagChild = None, footer: TagChild = None, ) -> NavSetCard: @@ -764,9 +762,7 @@ def navset_card_pill( id: Optional[str] = None, selected: Optional[str] = None, title: Optional[TagChild] = None, - sidebar: Optional[ - Sidebar - ] = None, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` include `Sidebar` and have the sidebar retrieved from within the args? + sidebar: Optional[Sidebar] = None, header: TagChild = None, footer: TagChild = None, placement: Literal["above", "below"] = "above", @@ -831,9 +827,7 @@ def navset_card_underline( id: Optional[str] = None, selected: Optional[str] = None, title: Optional[TagChild] = None, - sidebar: Optional[ - Sidebar - ] = None, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` include `Sidebar` and have the sidebar retrieved from within the args? + sidebar: Optional[Sidebar] = None, header: TagChild = None, footer: TagChild = None, placement: Literal["above", "below"] = "above", @@ -1009,9 +1003,7 @@ def __init__( title: TagChild, id: Optional[str], selected: Optional[str], - sidebar: Optional[ - Sidebar - ] = None, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` be of type `NavSetArg | Sidebar` and have the sidebar retrieved from within the args? + sidebar: Optional[Sidebar] = None, fillable: bool | list[str] = False, gap: Optional[CssUnit], padding: Optional[CssUnit | list[CssUnit]], @@ -1166,9 +1158,7 @@ def navset_bar( title: TagChild, id: Optional[str] = None, selected: Optional[str] = None, - sidebar: Optional[ - Sidebar - ] = None, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` include `Sidebar` and have the sidebar retrieved from within the args? + sidebar: Optional[Sidebar] = None, fillable: bool | list[str] = True, gap: Optional[CssUnit] = None, padding: Optional[CssUnit | list[CssUnit]] = None, diff --git a/shiny/ui/_page.py b/shiny/ui/_page.py index 56f9ff970..24a16ee7b 100644 --- a/shiny/ui/_page.py +++ b/shiny/ui/_page.py @@ -38,9 +38,7 @@ def page_sidebar( - sidebar: Sidebar - | TagChild - | TagAttrs, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` include `Sidebar` and have the sidebar retrieved from within the args? + sidebar: Sidebar, *args: TagChild | TagAttrs, title: Optional[str | Tag | TagList] = None, fillable: bool = True, @@ -110,9 +108,7 @@ def page_navbar( title: Optional[str | Tag | TagList] = None, id: Optional[str] = None, selected: Optional[str] = None, - sidebar: Optional[ - Sidebar - ] = None, # TODO-barret-API; Simlar to `layout_sidebar(*args: Sidebar | TagChild)`, should `*args` include `Sidebar` and have the sidebar retrieved from within the args? + sidebar: Optional[Sidebar] = None, # Only page_navbar gets enhanced treatement for `fillable` # If an `*args`'s `data-value` attr string is in `fillable`, then the component is fillable fillable: bool | list[str] = True, diff --git a/shiny/ui/_sidebar.py b/shiny/ui/_sidebar.py index c9a51ce8a..5a3abb429 100644 --- a/shiny/ui/_sidebar.py +++ b/shiny/ui/_sidebar.py @@ -31,12 +31,10 @@ "Sidebar", "sidebar", "layout_sidebar", - "toggle_sidebar", + "update_sidebar", # Legacy "panel_sidebar", "panel_main", - "DeprecatedPanelSidebar", - "DeprecatedPanelMain", ) @@ -175,10 +173,8 @@ def sidebar( open), `"closed"` or `False` (the sidebar starts closed), or `"always"` or `None` (the sidebar is always open and cannot be closed). - In :func:`~shiny.ui.toggle_sidebar`, `open` indicates the desired - state of the sidebar, where the default of `open = None` will cause the sidebar - to be toggled open if closed or vice versa. Note that - :func:`~shiny.ui.toggle_sidebar` can only open or close the + In :func:`~shiny.ui.update_sidebar`, `open` indicates the desired state of the + sidebar. Note that :func:`~shiny.ui.update_sidebar` can only open or close the sidebar, so it does not support the `"desktop"` and `"always"` options. id A character string. Required if wanting to re-actively read (or update) the @@ -292,8 +288,9 @@ def sidebar( @add_example() -def layout_sidebar( # TODO-barret-API; Should this be `layout_sidebar(*args, sidebar: Optional[Sidebar] = None)`? - *args: Sidebar | TagChild | TagAttrs, +def layout_sidebar( + sidebar: Sidebar, + *args: TagChild | TagAttrs, fillable: bool = True, fill: bool = True, bg: Optional[str] = None, @@ -359,7 +356,7 @@ def layout_sidebar( # TODO-barret-API; Should this be `layout_sidebar(*args, si * :func:`~shiny.ui.sidebar` """ - args, sidebar = _get_layout_sidebar_sidebar(args) + sidebar, args = _get_layout_sidebar_sidebar(sidebar, args) # TODO-future; implement # if fg is None and bg is not None: @@ -426,57 +423,63 @@ def layout_sidebar( # TODO-barret-API; Should this be `layout_sidebar(*args, si def _get_layout_sidebar_sidebar( - args: tuple[Sidebar | TagChild | TagAttrs, ...], -) -> tuple[tuple[TagChild | Sidebar | TagAttrs, ...], Sidebar]: - updated_args: list[Sidebar | TagChild | TagAttrs] = [] + sidebar: Sidebar, + args: tuple[TagChild | TagAttrs, ...], +) -> tuple[Sidebar, tuple[TagChild | TagAttrs, ...]]: + updated_args: list[TagChild | TagAttrs] = [] original_args = tuple(args) - sidebar: Sidebar | None = None - sidebar_orig_arg: Sidebar | DeprecatedPanelSidebar | None = None + # sidebar: Sidebar | None = None + sidebar_orig_arg: Sidebar | DeprecatedPanelSidebar = sidebar + + if isinstance(sidebar, DeprecatedPanelSidebar): + sidebar = sidebar.sidebar + + if not isinstance(sidebar, Sidebar): + raise ValueError( + "`layout_sidebar()` is not being supplied with a `sidebar()` object. Please supply a `sidebar()` object to `layout_sidebar(sidebar)`." + ) # Use `original_args` here so `updated_args` can be safely altered in place for i, arg in zip(range(len(original_args)), original_args): - if isinstance(arg, Sidebar): - if sidebar is not None: - raise ValueError( - "`layout_sidebar()` is being supplied with multiple `sidebar()` objects. Please supply only one `sidebar()` object to `layout_sidebar()`." - ) - sidebar = arg - elif isinstance(arg, DeprecatedPanelSidebar): - if i != 0: - raise ValueError( - "`panel_sidebar()` is not being used as the first argument to `layout_sidebar()`. `panel_sidebar()` has been deprecated and will go away in a future version of Shiny. Please supply `panel_sidebar()` arguments directly to `args` in `layout_sidebar(*args)` and use `sidebar()` instead of `panel_sidebar()`." - ) - sidebar_orig_arg = arg - sidebar = arg.sidebar + if isinstance(arg, DeprecatedPanelSidebar): + raise ValueError( + "`panel_sidebar()` is not being used as the first argument to `layout_sidebar(sidebar,)`. `panel_sidebar()` has been deprecated and will go away in a future version of Shiny. Please supply `panel_sidebar()` arguments directly to `args` in `layout_sidebar(sidebar)` and use `sidebar()` instead of `panel_sidebar()`." + ) + elif isinstance(arg, Sidebar): + raise ValueError( + "`layout_sidebar()` is being supplied with multiple `sidebar()` objects. Please supply only one `sidebar()` object to `layout_sidebar()`." + ) + elif isinstance(arg, DeprecatedPanelMain): - if i != 1: + if i != 0: raise ValueError( - "`panel_main()` is not being supplied as the second argument to `layout_sidebar()`. `panel_main()`/`panel_sidebar()` have been deprecated and will go away in a future version of Shiny. Please supply `panel_main()` arguments directly to `args` in `layout_sidebar(*args)` and use `sidebar()` instead of `panel_sidebar()`." + "`panel_main()` is not being supplied as the second argument to `layout_sidebar()`. `panel_main()`/`panel_sidebar()` have been deprecated and will go away in a future version of Shiny. Please supply `panel_main()` arguments directly to `args` in `layout_sidebar(sidebar, *args)` and use `sidebar()` instead of `panel_sidebar()`." ) if not isinstance(sidebar_orig_arg, DeprecatedPanelSidebar): raise ValueError( - "`panel_main()` is not being used with `panel_sidebar()`. `panel_main()`/`panel_sidebar()` have been deprecated and will go away in a future version of Shiny. Please supply `panel_main()` arguments directly to `args` in `layout_sidebar(*args)` and use `sidebar()` instead of `panel_sidebar()`." + "`panel_main()` is not being used with `panel_sidebar()`. `panel_main()`/`panel_sidebar()` have been deprecated and will go away in a future version of Shiny. Please supply `panel_main()` arguments directly to `args` in `layout_sidebar(sidebar, *args)` and use `sidebar()` instead of `panel_sidebar()`." ) - if len(args) > 3: + if len(args) > 2: raise ValueError( - "Unexpected extra legacy `*args` have been supplied to `layout_sidebar()` in addition to `panel_main()` or `panel_sidebar()`. `panel_main()` has been deprecated and will go away in a future version of Shiny. Please supply `panel_main()` arguments directly to `args` in `layout_sidebar(*args)` and use `sidebar()` instead of `panel_sidebar()`." + "Unexpected extra legacy `*args` have been supplied to `layout_sidebar()` in addition to `panel_main()` or `panel_sidebar()`. `panel_main()` has been deprecated and will go away in a future version of Shiny. Please supply `panel_main()` arguments directly to `args` in `layout_sidebar(sidebar, *args)` and use `sidebar()` instead of `panel_sidebar()`." ) # Notes for this point in the code: - # * We are working with args[1], a `DeprecatedPanelMain`; args[0] is `DeprecatedPanelSidebar` - # * len(args) == 2 or 3 + # * We are working with args[0], a `DeprecatedPanelMain`; sidebar was originally a `DeprecatedPanelSidebar` + # * len(args) == 1 or 2 # Handle legacy `layout_sidebar(sidebar, main, position=)` value - if len(args) == 3: - arg2 = args[2] - if not (arg2 == "left" or arg2 == "right"): + if len(args) == 2: + arg1 = args[1] + if not (arg1 == "left" or arg1 == "right"): raise ValueError( - "layout_sidebar(*args) contains non-valid legacy values. Please use `sidebar()` instead of `panel_sidebar()` and supply any `panel_main()` arguments directly to `args` in `layout_sidebar(*args)`." + "layout_sidebar(*args) contains non-valid legacy values. Please use `sidebar()` instead of `panel_sidebar()` and supply any `panel_main()` arguments directly to `args` in `layout_sidebar(sidebar, *args)`." ) # We know `sidebar_orig_arg` is a `DeprecatedPanelSidebar` here sidebar.position = cast( # pyright: ignore[reportOptionalMemberAccess] - Literal["left", "right"], arg2 + Literal["left", "right"], + arg1, ) # Only keep panel_main content @@ -490,34 +493,27 @@ def _get_layout_sidebar_sidebar( # Keep the arg! updated_args.append(arg) - if sidebar is None: - raise ValueError( - "`layout_sidebar()` did not receive a `sidebar()` object. To use `layout_sidebar()`, please supply a `sidebar()` object." - ) - - return (tuple(updated_args), sidebar) + return (sidebar, tuple(updated_args)) @add_example() -def toggle_sidebar( # TODO-barret-API; Rename to `update_sidebar()` +def update_sidebar( id: str, - open: Literal["toggle", "open", "closed", "always"] | bool | None = None, - session: Session | None = None, + *, + show: Optional[bool] = None, + session: Optional[Session] = None, ) -> None: """ - Toggle a sidebar + Update a sidebar's visibility - Toggle a :func:`~shiny.ui.sidebar` state during an active Shiny user session. + Set a :func:`~shiny.ui.sidebar` state during an active Shiny user session. Parameters ---------- id The `id` of the :func:`~shiny.ui.sidebar` to toggle. - open - The desired state of the sidebar, choosing from the following options: `None` - (toggle sidebar open and closed), `"open"` or `True` (open the sidebar), - `"closed"` or `False` (close the sidebar). Note that `toggle_sidebar()` can only - open or close the sidebar, so it does not support the `"desktop"` and `"always"` + show + The desired visible state of the sidebar, where `True` opens the sidebar and `False` closes the sidebar (if not already in that state). session A Shiny session object (the default should almost always be used). @@ -528,29 +524,28 @@ def toggle_sidebar( # TODO-barret-API; Rename to `update_sidebar()` """ session = require_active_session(session) - method: Literal["toggle", "open", "close"] - if open is None or open == "toggle": - method = "toggle" - elif open is True or open == "open": - method = "open" - elif open is False or open == "closed": - method = "close" - else: - if open == "always" or open == "desktop": - raise ValueError( - f"`open = '{open}'` is not supported by `toggle_sidebar()`" - ) - raise ValueError( - "open must be NULL (or 'toggle'), TRUE (or 'open'), or FALSE (or 'closed')" - ) - - def callback() -> None: - session.send_input_message(id, {"method": method}) - - session.on_flush(callback, once=True) - - -_sidebar_func = sidebar + # method: Literal["toggle", "open", "close"] + # if open is None or open == "toggle": + # method = "toggle" + # elif open is True or open == "open": + # method = "open" + # elif open is False or open == "closed": + # method = "close" + # else: + # if open == "always" or open == "desktop": + # raise ValueError( + # f"`open = '{open}'` is not supported by `update_sidebar()`" + # ) + # raise ValueError( + # "open must be NULL (or 'toggle'), TRUE (or 'open'), or FALSE (or 'closed')" + # ) + if show is not None: + method = "open" if bool(show) else "close" + + def callback() -> None: + session.send_input_message(id, {"method": method}) + + session.on_flush(callback, once=True) def _collapse_icon() -> TagChild: @@ -618,7 +613,15 @@ def panel_main( # Deprecated 2023-06-13 -class DeprecatedPanelSidebar: + + +# This class should be removed when `panel_sidebar()` is removed +class DeprecatedPanelSidebar( + # While it doesn't seem right to inherit from `Sidebar`, it's the easiest way to + # make sure `layout_sidebar(sidebar: Sidebar)` works without mucking up the + # function signature. + Sidebar +): """ [Deprecated] Sidebar panel @@ -669,6 +672,8 @@ def tagify(self) -> Tag: return self.sidebar.tag.tagify() +# This class should be removed when `panel_main()` is removed +# Must be `Tagifiable`, so it can fit as a type `TagChild` class DeprecatedPanelMain: """ [Deprecated] Main panel diff --git a/shiny/ui/fill/__init__.py b/shiny/ui/fill/__init__.py index ad75ff274..53551d7d9 100644 --- a/shiny/ui/fill/__init__.py +++ b/shiny/ui/fill/__init__.py @@ -2,14 +2,14 @@ as_fill_item, as_fillable_container, remove_all_fill, - is_fill_item, - is_fillable_container, + # is_fill_item, + # is_fillable_container, ) __all__ = ( "as_fillable_container", "as_fill_item", "remove_all_fill", - "is_fillable_container", - "is_fill_item", + # "is_fillable_container", + # "is_fill_item", ) diff --git a/shiny/ui/fill/_fill.py b/shiny/ui/fill/_fill.py index 024062d1f..87f58bd37 100644 --- a/shiny/ui/fill/_fill.py +++ b/shiny/ui/fill/_fill.py @@ -12,8 +12,8 @@ "as_fillable_container", "as_fill_item", "remove_all_fill", - "is_fill_item", - "is_fillable_container", + # "is_fill_item", + # "is_fillable_container", ) TagT = TypeVar("TagT", bound="Tag") @@ -31,7 +31,8 @@ @add_example() -def as_fillable_container( # TODO-barret-API; These methods mutate their input values. Should they return a new object instead? Or not return at all (like `LIST.sort()`)? +# TODO-future-API; These methods mutate their input values. Should they return a new object instead? Or not return at all (like `LIST.sort()`)? +def as_fillable_container( tag: TagT, ) -> TagT: tag_prepend_class(tag, FILL_CONTAINER_CLASS) @@ -40,7 +41,8 @@ def as_fillable_container( # TODO-barret-API; These methods mutate their input @add_example() -def as_fill_item( # TODO-barret-API; These methods mutate their input values. Should they return a new object instead? Or not return at all (like `LIST.sort()`)? +# TODO-future-API; These methods mutate their input values. Should they return a new object instead? Or not return at all (like `LIST.sort()`)? +def as_fill_item( tag: TagT, ) -> TagT: """ @@ -69,8 +71,6 @@ def as_fill_item( # TODO-barret-API; These methods mutate their input values. S -------- * :func:`~shiny.ui.fill.as_fillable_container` * :func:`~shiny.ui.fill.remove_all_fill` - * :func:`~shiny.ui.fill.is_fill_item` - * :func:`~shiny.ui.fill.is_fillable_container` """ tag_prepend_class(tag, FILL_ITEM_CLASS) tag.append(fill_dependency()) @@ -107,8 +107,6 @@ def remove_all_fill( -------- * :func:`~shiny.ui.fill.as_fill_item` * :func:`~shiny.ui.fill.as_fillable_container` - * :func:`~shiny.ui.fill.is_fill_item` - * :func:`~shiny.ui.fill.is_fillable_container` """ tag_remove_class(tag, FILL_CONTAINER_CLASS) @@ -116,7 +114,8 @@ def remove_all_fill( return tag -def is_fillable_container( # TODO-barret-api; Should this be exported? +# Method currently not exposed, but implemented within bslib +def is_fillable_container( tag: object, ) -> bool: """ @@ -146,8 +145,6 @@ def is_fillable_container( # TODO-barret-api; Should this be exported? * :func:`~shiny.ui.fill.as_fill_item` * :func:`~shiny.ui.fill.as_fillable_container` * :func:`~shiny.ui.fill.remove_all_fill` - * :func:`~shiny.ui.fill.is_fill_item` - * :func:`~shiny.ui.fill.is_fillable_container` """ # TODO-future; Handle widgets # # won't actually work until (htmltools#334) gets fixed @@ -156,7 +153,8 @@ def is_fillable_container( # TODO-barret-api; Should this be exported? return isinstance(tag, Tag) and tag.has_class(FILL_CONTAINER_CLASS) -def is_fill_item(tag: object) -> bool: # TODO-barret-api; Should this be exported? +# Method currently not exposed, but implemented within bslib +def is_fill_item(tag: object) -> bool: """ Test a tag for being a fill item @@ -183,7 +181,6 @@ def is_fill_item(tag: object) -> bool: # TODO-barret-api; Should this be export * :func:`~shiny.ui.fill.as_fill_item` * :func:`~shiny.ui.fill.as_fillable_container` * :func:`~shiny.ui.fill.remove_all_fill` - * :func:`~shiny.ui.fill.is_fillable_container` """ # TODO-future; Handle widgets # # won't actually work until (htmltools#334) gets fixed diff --git a/tests/e2e/TODO/sidebar/app.py b/tests/e2e/TODO/sidebar/app.py index fc30b92d8..8e71a34ce 100644 --- a/tests/e2e/TODO/sidebar/app.py +++ b/tests/e2e/TODO/sidebar/app.py @@ -65,24 +65,24 @@ def ui_content(): @reactive.Effect @reactive.event(input.open_all) def _(): - ui.toggle_sidebar("sidebar_inner", open=True) - ui.toggle_sidebar("sidebar_outer", open=True) + ui.update_sidebar("sidebar_inner", show=True) + ui.update_sidebar("sidebar_outer", show=True) @reactive.Effect @reactive.event(input.close_all) def _(): - ui.toggle_sidebar("sidebar_inner", open=False) - ui.toggle_sidebar("sidebar_outer", open=False) + ui.update_sidebar("sidebar_inner", show=False) + ui.update_sidebar("sidebar_outer", show=False) @reactive.Effect @reactive.event(input.toggle_inner) def _(): - ui.toggle_sidebar("sidebar_inner") + ui.update_sidebar("sidebar_inner", show=not input.sidebar_inner()) @reactive.Effect @reactive.event(input.toggle_outer) def _(): - ui.toggle_sidebar("sidebar_outer") + ui.update_sidebar("sidebar_outer", show=not input.sidebar_outer()) app = App(app_ui, server) diff --git a/tests/e2e/TODO/update_popover/app.py b/tests/e2e/TODO/update_popover/app.py index 30a83d7eb..55b6d1c2b 100644 --- a/tests/e2e/TODO/update_popover/app.py +++ b/tests/e2e/TODO/update_popover/app.py @@ -17,7 +17,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect def _(): # Immediately display popover - ui.toggle_popover("popover_id", show=True) + ui.update_popover("popover_id", show=True) @reactive.Effect @reactive.event(input.btn_update) @@ -26,8 +26,12 @@ def _(): "A " + " ".join(["NEW" for _ in range(input.btn_update())]) + " message" ) - ui.update_popover("popover_id", content) - # ui.toggle_popover("popover_id", show=True) + ui.update_popover( + "popover_id", + content, + # # Causes bug. Skipping for now + # show=True + ) @reactive.Effect def _(): diff --git a/tests/e2e/bugs/0696-resolve-id/app.py b/tests/e2e/bugs/0696-resolve-id/app.py index 8e8562b99..af9e894dd 100644 --- a/tests/e2e/bugs/0696-resolve-id/app.py +++ b/tests/e2e/bugs/0696-resolve-id/app.py @@ -98,7 +98,6 @@ def ui_navs(label: str) -> list[ui._navs.Nav]: """ ), ui.layout_column_wrap( - 1 / 2, *[ ui.output_text_verbatim(f"status_x_{x_input_key}", placeholder=True) for x_input_key in x_input_keys @@ -107,6 +106,7 @@ def ui_navs(label: str) -> list[ui._navs.Nav]: ui.output_text_verbatim(f"status_{input_key}", placeholder=True) for input_key in input_keys ], + width=1 / 2, gap="2px", heights_equal="row", ), @@ -372,10 +372,10 @@ def preprocess_file(x: Any) -> str: id="explanation", ), ui.layout_column_wrap( - 1 / 3, mod_x_ui("", "Global"), # "" == Root mod_x_ui("mod1", "Module 1"), mod_x_ui("mod2", "Module 2"), + width=1 / 3, ), # ui.h3("Inputs that are not in a module:"), # ui.output_text_verbatim("not_modules", placeholder=True), @@ -391,7 +391,7 @@ def server(input: Inputs, output: Outputs, session: Session): # https://stackoverflow.com/a/64523765/591574 if hasattr(sys, "ps1"): # Open the explanation popover - ui.toggle_popover("explanation", show=True) + ui.update_popover("explanation", show=True) # On button clicks, hide the explanation popover @reactive.Effect(suspended=True) @@ -400,7 +400,7 @@ def _(): input.update_global() input.update_mod1() input.update_mod2() - ui.toggle_popover("explanation", show=False) + ui.update_popover("explanation", show=False) # Master function to update a module's features def update_session( @@ -411,7 +411,7 @@ def update_session( letter = letters[count % len(letters)] on_off = count % 2 == 1 with session_context(session): - ui.accordion_panel_set("accordion", letter) + ui.update_accordion("accordion", show=letter) ui.update_checkbox("input_checkbox", value=on_off) checkbox_group_letters = letters.copy() @@ -441,7 +441,7 @@ def update_session( # https://github.com/posit-dev/py-shiny/issues/716 # ui.update_sidebar("sidebar", value=True) if session.input.sidebar() is False: - ui.toggle_sidebar("sidebar") + ui.update_sidebar("sidebar", show=True) # Turn off switch ui.update_switch("input_switch", value=False) @@ -450,20 +450,20 @@ def update_session( # https://github.com/posit-dev/py-shiny/issues/717 # ui.update_popover("popover", show=False) if session.input.popover() is True: - ui.toggle_popover("popover") + ui.update_popover("popover", show=False) # Hide tooltip # https://github.com/posit-dev/py-shiny/issues/717 if session.input.tooltip() is True: - ui.toggle_tooltip("tooltip") + ui.update_tooltip("tooltip", show=False) else: if session.input.sidebar() == on_off: - ui.toggle_sidebar("sidebar") + ui.update_sidebar("sidebar", show=not on_off) if session.input.input_switch() != on_off: - ui.toggle_switch("input_switch") + ui.update_switch("input_switch", value=on_off) if session.input.popover() != on_off: - ui.toggle_popover("popover") + ui.update_popover("popover", show=on_off) if session.input.tooltip() != on_off: - ui.toggle_tooltip("tooltip") + ui.update_tooltip("tooltip", show=on_off) ui.update_navs("navset_bar", selected=letter) ui.update_navs("navset_card_pill", selected=letter) diff --git a/tests/e2e/bugs/0696-resolve-id/test_0696_resolve_id.py b/tests/e2e/bugs/0696-resolve-id/test_0696_resolve_id.py index f397f58f8..d04618976 100644 --- a/tests/e2e/bugs/0696-resolve-id/test_0696_resolve_id.py +++ b/tests/e2e/bugs/0696-resolve-id/test_0696_resolve_id.py @@ -5,6 +5,7 @@ import os from pathlib import Path +import pytest from conftest import ShinyAppProc from controls import ( DownloadButton, @@ -105,6 +106,8 @@ def expect_default_outputs(page: Page, module_id: str): expect_outputs(page, module_id, "a", 0) +# Sidebars do not seem to work on webkit. Skipping test on webkit +@pytest.mark.skip_browser("webkit") def test_module_support(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) diff --git a/tests/e2e/ui/accordion/app.py b/tests/e2e/ui/accordion/app.py index e36805059..e2b33fb25 100644 --- a/tests/e2e/ui/accordion/app.py +++ b/tests/e2e/ui/accordion/app.py @@ -42,19 +42,19 @@ def _(): with reactive.isolate(): if "Section B" in acc(): - ui.accordion_panel_close("acc", "Section B") + ui.update_accordion_panel("acc", "Section B", show=False) else: - ui.accordion_panel_open("acc", "Section B") + ui.update_accordion_panel("acc", "Section B", show=True) @reactive.Effect def _(): req(input.open_all()) - ui.accordion_panel_open("acc", True) + ui.update_accordion("acc", show=True) @reactive.Effect def _(): req(input.close_all()) - ui.accordion_panel_close("acc", True) + ui.update_accordion("acc", show=False) has_efg = False has_alternate = True @@ -76,7 +76,7 @@ def _(): nonlocal has_alternate val = int(has_alternate) sections = [section for i, section in enumerate(sections) if i % 2 == val] - ui.accordion_panel_set("acc", sections) + ui.update_accordion("acc", show=sections) has_alternate = not has_alternate @reactive.Effect @@ -85,11 +85,11 @@ def _(): nonlocal has_efg if has_efg: - ui.accordion_panel_remove("acc", ["Section E", "Section F", "Section G"]) + ui.remove_accordion_panel("acc", ["Section E", "Section F", "Section G"]) else: - ui.accordion_panel_insert("acc", make_panel("G"), "Section F") - ui.accordion_panel_insert("acc", make_panel("F"), "Section E") - ui.accordion_panel_insert("acc", make_panel("E"), "Section D") + ui.insert_accordion_panel("acc", make_panel("G"), "Section F") + ui.insert_accordion_panel("acc", make_panel("F"), "Section E") + ui.insert_accordion_panel("acc", make_panel("E"), "Section D") has_efg = not has_efg @@ -112,7 +112,7 @@ def _(): # print(acc()) if "Section A" not in acc(): ui.notification_show("Opening Section A", duration=2) - ui.accordion_panel_open("acc", "Section A") + ui.update_accordion_panel("acc", "Section A", show=True) ui.update_accordion_panel( "acc", "Section A", diff --git a/tests/e2e/ui/popover/app.py b/tests/e2e/ui/popover/app.py index d19a74afc..45ea2191b 100644 --- a/tests/e2e/ui/popover/app.py +++ b/tests/e2e/ui/popover/app.py @@ -4,8 +4,6 @@ ui.input_action_button("btn_show", "Show popover", class_="mt-3 me-3"), ui.input_action_button("btn_close", "Close popover", class_="mt-3 me-3"), ui.br(), - ui.input_action_button("btn_toggle", "Toggle popover", class_="mt-3 me-3"), - ui.br(), ui.br(), ui.popover( ui.input_action_button("btn_w_popover", "A button w/ a popover", class_="mt-3"), @@ -21,19 +19,13 @@ def server(input: Inputs, output: Outputs, session: Session): def _(): req(input.btn_show()) - ui.toggle_popover("popover_id", show=True) + ui.update_popover("popover_id", show=True) @reactive.Effect def _(): req(input.btn_close()) - ui.toggle_popover("popover_id", show=False) - - @reactive.Effect - def _(): - req(input.btn_toggle()) - - ui.toggle_popover("popover_id") + ui.update_popover("popover_id", show=False) @reactive.Effect def _(): diff --git a/tests/e2e/ui/tooltip/app.py b/tests/e2e/ui/tooltip/app.py index b6fd67061..fcf6cb208 100644 --- a/tests/e2e/ui/tooltip/app.py +++ b/tests/e2e/ui/tooltip/app.py @@ -4,8 +4,6 @@ ui.input_action_button("btn_show", "Show tooltip", class_="mt-3 me-3"), ui.input_action_button("btn_close", "Close tooltip", class_="mt-3 me-3"), ui.br(), - ui.input_action_button("btn_toggle", "Toggle tooltip", class_="mt-3 me-3"), - ui.br(), ui.br(), ui.tooltip( ui.input_action_button("btn_w_tooltip", "A button w/ a tooltip", class_="mt-3"), @@ -21,19 +19,13 @@ def server(input: Inputs, output: Outputs, session: Session): def _(): req(input.btn_show()) - ui.toggle_tooltip("tooltip_id", show=True) + ui.update_tooltip("tooltip_id", show=True) @reactive.Effect def _(): req(input.btn_close()) - ui.toggle_tooltip("tooltip_id", show=False) - - @reactive.Effect - def _(): - req(input.btn_toggle()) - - ui.toggle_tooltip("tooltip_id") + ui.update_tooltip("tooltip_id", show=False) @reactive.Effect def _(): diff --git a/tests/pytest/test_sidebar.py b/tests/pytest/test_sidebar.py index 1b38d31c0..0f728019f 100644 --- a/tests/pytest/test_sidebar.py +++ b/tests/pytest/test_sidebar.py @@ -16,8 +16,6 @@ def test_panel_main_and_panel_sidebar(): # OK ui.layout_sidebar(_s) ui.layout_sidebar(_s, None) - ui.layout_sidebar(None, _s) - ui.layout_sidebar(None, _s, None) try: ui.layout_sidebar(_s, _s) @@ -26,10 +24,10 @@ def test_panel_main_and_panel_sidebar(): assert "multiple `sidebar()` objects" in str(e) try: - ui.layout_sidebar(None, _ps) + ui.layout_sidebar(None, _ps) # pyright: ignore[reportGeneralTypeIssues] raise AssertionError("Should have raised ValueError") except ValueError as e: - assert "not being used as the first argument" in str(e) + assert "not being supplied with a `sidebar()` object." in str(e) try: ui.layout_sidebar(_s, _pm) @@ -37,11 +35,6 @@ def test_panel_main_and_panel_sidebar(): except ValueError as e: assert "is not being used with `panel_sidebar()`" in str(e) - try: - ui.layout_sidebar(_pm) - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "not being supplied as the second argument" in str(e) try: ui.layout_sidebar(_ps, None, _pm) raise AssertionError("Should have raised ValueError") @@ -52,9 +45,3 @@ def test_panel_main_and_panel_sidebar(): raise AssertionError("Should have raised ValueError") except ValueError as e: assert "Unexpected extra legacy `*args`" in str(e) - - try: - ui.layout_sidebar(_m) - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "did not receive a `sidebar()`" in str(e)