Skip to content

Commit 18a72bc

Browse files
authored
Merge branch 'main' into feat/readme-porting-from-bslib
2 parents f3ceb9c + a8f5217 commit 18a72bc

File tree

7 files changed

+56
-37
lines changed

7 files changed

+56
-37
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
* Fixed `ui.tooltip()`'s `options` parameter to properly pass Bootstrap tooltip options to the underlying web component. (#2101)
1919

20+
* Fixed an issue where `session.bookmark()` would error when non-existent `input` values are read. (#2117)
21+
22+
* Revised `accordion()`'s `open` logic to close all panels when an empty list is passed. (#2109)
23+
2024
## [1.5.0] - 2025-09-11
2125

2226
### New features

shiny/express/ui/_cm_components.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ... import ui
1010
from ..._deprecated import warn_deprecated
1111
from ..._docstring import add_example, no_example
12-
from ...types import DEPRECATED, MISSING, MISSING_TYPE
12+
from ...types import DEPRECATED, MISSING, MISSING_TYPE, ListOrTuple
1313
from ...ui._accordion import AccordionPanel
1414
from ...ui._card import CardItem
1515
from ...ui._layout_columns import BreakpointsUser
@@ -649,7 +649,7 @@ def card_footer(
649649
def accordion(
650650
*,
651651
id: Optional[str] = None,
652-
open: Optional[bool | str | list[str]] = None,
652+
open: Optional[bool | str | ListOrTuple[str]] = None,
653653
multiple: bool = True,
654654
class_: Optional[str] = None,
655655
width: Optional[CssUnit] = None,
@@ -669,11 +669,12 @@ def accordion(
669669
value will correspond to the :func:`~shiny.ui.accordion_panel`'s
670670
`value` argument.
671671
open
672-
A list of :func:`~shiny.ui.accordion_panel` values to open (i.e.,
673-
show) by default. The default value of `None` will open the first
674-
:func:`~shiny.ui.accordion_panel`. Use a value of `True` to open
675-
all (or `False` to open none) of the items. It's only possible to open more than
676-
one panel when `multiple=True`.
672+
A `str` or list of `str` naming the :func:`~shiny.ui.accordion_panel`
673+
value(s) to open (i.e., show) by default. (An empty list closes all panels.)
674+
The default value of `None` will open the first
675+
:func:`~shiny.ui.accordion_panel`. Use a value of `True` to open all (or `False`
676+
to open none) of the items. It's only possible to open more than one panel when
677+
`multiple=True`.
677678
multiple
678679
Whether multiple :func:`~shiny.ui.accordion_panel` can be open at
679680
once.

shiny/session/_session.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1477,7 +1477,11 @@ async def _serialize(
14771477
continue
14781478
if key in exclude_set:
14791479
continue
1480-
val = value()
1480+
1481+
try:
1482+
val = value()
1483+
except SilentException:
1484+
continue
14811485

14821486
# Possibly apply custom serialization given the input id
14831487
serializer = self._serializers.get(key, serializer_default)

shiny/ui/_accordion.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .._utils import drop_none, private_random_id
1010
from ..bookmark import restore_input
1111
from ..session import require_active_session
12-
from ..types import MISSING, MISSING_TYPE
12+
from ..types import MISSING, MISSING_TYPE, ListOrTuple
1313
from ._html_deps_shinyverse import components_dependencies
1414
from ._tag import consolidate_attrs
1515
from .css._css_unit import CssUnit, as_css_unit
@@ -176,7 +176,7 @@ def tagify(self) -> Tag:
176176
def accordion(
177177
*args: AccordionPanel | TagAttrs,
178178
id: Optional[str] = None,
179-
open: Optional[bool | str | list[str]] = None,
179+
open: Optional[bool | str | ListOrTuple[str]] = None,
180180
multiple: bool = True,
181181
class_: Optional[str] = None,
182182
width: Optional[CssUnit] = None,
@@ -198,11 +198,12 @@ def accordion(
198198
value will correspond to the :func:`~shiny.ui.accordion_panel`'s
199199
`value` argument.
200200
open
201-
A list of :func:`~shiny.ui.accordion_panel` values to open (i.e.,
202-
show) by default. The default value of `None` will open the first
203-
:func:`~shiny.ui.accordion_panel`. Use a value of `True` to open
204-
all (or `False` to open none) of the items. It's only possible to open more than
205-
one panel when `multiple=True`.
201+
A `str` or list of `str` naming the :func:`~shiny.ui.accordion_panel`
202+
value(s) to open (i.e., show) by default. (An empty list closes all panels.)
203+
The default value of `None` will open the first
204+
:func:`~shiny.ui.accordion_panel`. Use a value of `True` to open all (or `False`
205+
to open none) of the items. It's only possible to open more than one panel when
206+
`multiple=True`.
206207
multiple
207208
Whether multiple :func:`~shiny.ui.accordion_panel` can be open at
208209
once.
@@ -259,21 +260,19 @@ def accordion(
259260
)
260261
open = restore_input(accordion_id, open)
261262

262-
is_open: list[bool] = []
263-
if open is None:
264-
is_open = [False for _ in panels]
263+
is_open: list[bool]
264+
if has_restored_input and open is None:
265+
# None from restore_input indicates all panels closed
266+
is_open = [False] * len(panels)
267+
elif open is None:
268+
# otherwise None indicates default behavior (open first panel)
269+
is_open = [i == 0 for i in range(len(panels))]
265270
elif isinstance(open, bool):
266-
is_open = [open for _ in panels]
271+
is_open = [open] * len(panels)
267272
else:
268-
if not isinstance(open, list):
269-
open = [open]
270-
#
271-
is_open = [panel._data_value in open for panel in panels]
272-
273-
if not has_restored_input:
274-
# Open the first panel by default
275-
if open is not False and len(is_open) > 0 and not any(is_open):
276-
is_open[0] = True
273+
# str | ListOrTuple[str] -> set
274+
open_set = {open} if isinstance(open, str) else set(open)
275+
is_open = [panel._data_value in open_set for panel in panels]
277276

278277
if (not multiple) and sum(is_open) > 1:
279278
raise ValueError("Can't select more than one panel when `multiple = False`")
@@ -384,11 +383,11 @@ def _accordion_panel_action(
384383
*,
385384
id: str,
386385
method: str,
387-
values: bool | str | list[str],
386+
values: bool | str | ListOrTuple[str],
388387
session: Session | None,
389388
) -> None:
390389
if not isinstance(values, bool):
391-
if not isinstance(values, list):
390+
if not isinstance(values, (list, tuple)):
392391
values = [values]
393392
_assert_list_str(values)
394393

@@ -404,7 +403,7 @@ def _accordion_panel_action(
404403
def update_accordion(
405404
id: str,
406405
*,
407-
show: bool | str | list[str],
406+
show: bool | str | ListOrTuple[str],
408407
session: Optional[Session] = None,
409408
) -> None:
410409
"""
@@ -502,7 +501,7 @@ def insert_accordion_panel(
502501
@add_example()
503502
def remove_accordion_panel(
504503
id: str,
505-
target: str | list[str],
504+
target: str | ListOrTuple[str],
506505
session: Optional[Session] = None,
507506
) -> None:
508507
"""
@@ -513,7 +512,8 @@ def remove_accordion_panel(
513512
id
514513
A string that matches an existing :func:`~shiny.ui.accordion`'s `id`.
515514
target
516-
The `value` of an existing panel to remove.
515+
A string or list of strings used to identify particular
516+
:func:`~shiny.ui.accordion_panel`(s) by their `value`.
517517
session
518518
A Shiny session object (the default should almost always be used).
519519
@@ -529,7 +529,7 @@ def remove_accordion_panel(
529529
* :func:`~shiny.ui.insert_accordion_panel`
530530
* :func:`~shiny.ui.update_accordion_panel`
531531
"""
532-
if not isinstance(target, list):
532+
if not isinstance(target, (list, tuple)):
533533
target = [target]
534534

535535
_send_panel_message(
@@ -632,8 +632,8 @@ def _assert_str(x: str) -> str:
632632
return x
633633

634634

635-
def _assert_list_str(x: list[str]) -> list[str]:
636-
if not isinstance(x, list):
635+
def _assert_list_str(x: ListOrTuple[str]) -> ListOrTuple[str]:
636+
if not isinstance(x, (list, tuple)):
637637
raise TypeError(f"Expected list, got {type(x)}")
638638
for i, x_i in enumerate(x):
639639
if not isinstance(x_i, str):

tests/playwright/shiny/bookmark/modal/app.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,19 @@ def app_ui(request: Request):
99
)
1010

1111

12-
def server(input: Inputs, ouput: Outputs, session: Session):
12+
def server(input: Inputs, output: Outputs, session: Session):
1313

1414
@reactive.effect
1515
@reactive.event(input.letter, ignore_init=True)
1616
async def _():
1717
await session.bookmark()
1818

19+
# Just to make sure that missing inputs don't break bookmarking
20+
# behavior (https://github.com/posit-dev/py-shiny/pull/2117)
21+
@reactive.effect
22+
@reactive.event(input.non_existent_input)
23+
def _():
24+
pass
25+
1926

2027
app = App(app_ui, server, bookmark_store="url")

tests/playwright/shiny/bookmark/modal/test_bookmark_modal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ def test_bookmark_modules(page: Page, local_app: ShinyAppProc):
1919
)
2020

2121
assert "?" not in page.url
22+
assert "An error has occurred" not in page.inner_text("body")

tests/playwright/shiny/components/selectize/test_selectize.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# import pytest
22
from playwright.sync_api import Page, expect
3+
from utils.deploy_utils import skip_if_not_chrome
34

45
from shiny.playwright import controller
56
from shiny.run import ShinyAppProc
67

78

9+
@skip_if_not_chrome # trouble with firefox. ??
810
def test_selectize(page: Page, local_app: ShinyAppProc) -> None:
911
page.goto(local_app.url)
1012

0 commit comments

Comments
 (0)