Description
In 1.6.1, if an output finishes recalculating while its containing navset_tab panel is hidden, the recalculating overlay never clears. It appears as a "white-out" dimming effect over the output that persists until the user clicks on it.
This worked correctly in 1.6.0.
Reported by sebovzeoueb on Discord.
Steps to Reproduce
- Run the app below
- Wait for the page to load — collections populate after a 1.5s async delay
- Switch to the Fixed tab before the 1.5s delay completes
- Switch back to Buggy (or Fixed) — the dropdown shows a persistent white-out overlay
- Clicking the overlay clears it
import asyncio
from dataclasses import dataclass
from typing import Dict
from shiny import App, module, reactive, ui, req, render, Inputs, Outputs, Session
@dataclass
class CollectionInfo:
collection_id: str
collection_name: str
@dataclass
class CollectionsData:
collections: Dict[str, CollectionInfo]
MOCK_COLLECTIONS = CollectionsData(
collections={
"col_1": CollectionInfo("col_1", "Alpha Collection"),
"col_2": CollectionInfo("col_2", "Beta Collection"),
"col_3": CollectionInfo("col_3", "Gamma Collection"),
}
)
@module.ui
def collection_selector_ui():
return ui.output_ui("collection_selector")
@module.server
def collection_selector_server(
input: Inputs,
output: Outputs,
session: Session,
selected_collection: reactive.Value,
collections: reactive.Value,
):
@render.ui
def collection_selector():
req(collections.get())
return ui.input_select(
id="internal_selected_collection",
label="Select Collection",
choices={
c.collection_id: c.collection_name
for c in collections.get().collections.values()
},
selected=selected_collection.get(),
)
@reactive.effect
def update_selection():
selected_collection.set(input.internal_selected_collection())
app_ui = ui.page_fluid(
ui.navset_tab(
ui.nav_panel(
"Tab A",
ui.card(collection_selector_ui("a"), ui.output_text("a_selected")),
),
ui.nav_panel(
"Tab B",
ui.card(collection_selector_ui("b"), ui.output_text("b_selected")),
),
id="tabs",
),
)
def server(input: Inputs, output: Outputs, session: Session):
a_collection = reactive.Value(None)
b_collection = reactive.Value(None)
collections: reactive.Value = reactive.Value(None)
@reactive.effect
async def load_collections():
await asyncio.sleep(1.5)
collections.set(MOCK_COLLECTIONS)
a_collection.set("col_1")
b_collection.set("col_1")
collection_selector_server("a", selected_collection=a_collection, collections=collections)
collection_selector_server("b", selected_collection=b_collection, collections=collections)
@render.text
def a_selected():
val = a_collection.get()
return f"Selected: {val}" if val else "Loading..."
@render.text
def b_selected():
val = b_collection.get()
return f"Selected: {val}" if val else "Loading..."
app = App(app_ui, server)
Expected Behavior
The recalculating overlay clears as soon as the tab becomes visible, regardless of when the recalculation completed.
Actual Behavior
If the output finishes recalculating while its tab is hidden, switching back to the tab leaves the overlay stuck. A click dismisses it.
Root Cause (hypothesis)
1.6.1 replaced jQuery shown/hidden event listeners with native ResizeObserver/IntersectionObserver for output visibility detection (#1415). The jQuery shown event fired synchronously on tab switch, giving Shiny a reliable hook to clear any pending overlay. IntersectionObserver is asynchronous — if an output completes recalculation while not intersecting the viewport (hidden tab), there is no subsequent intersection change event when the tab is revealed, so the overlay is never dismissed.
The click-to-dismiss behavior suggests the output state itself is correct server-side; only the overlay CSS class is stuck.
Version
- Affected: 1.6.1
- Working: 1.6.0
Description
In 1.6.1, if an output finishes recalculating while its containing
navset_tabpanel is hidden, the recalculating overlay never clears. It appears as a "white-out" dimming effect over the output that persists until the user clicks on it.This worked correctly in 1.6.0.
Reported by sebovzeoueb on Discord.
Steps to Reproduce
Expected Behavior
The recalculating overlay clears as soon as the tab becomes visible, regardless of when the recalculation completed.
Actual Behavior
If the output finishes recalculating while its tab is hidden, switching back to the tab leaves the overlay stuck. A click dismisses it.
Root Cause (hypothesis)
1.6.1 replaced jQuery
shown/hiddenevent listeners with nativeResizeObserver/IntersectionObserverfor output visibility detection (#1415). The jQueryshownevent fired synchronously on tab switch, giving Shiny a reliable hook to clear any pending overlay.IntersectionObserveris asynchronous — if an output completes recalculation while not intersecting the viewport (hidden tab), there is no subsequent intersection change event when the tab is revealed, so the overlay is never dismissed.The click-to-dismiss behavior suggests the output state itself is correct server-side; only the overlay CSS class is stuck.
Version