Skip to content

Recalculating overlay gets stuck on outputs inside hidden navset tabs (regression in 1.6.1) #2263

@elnelson575

Description

@elnelson575

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

  1. Run the app below
  2. Wait for the page to load — collections populate after a 1.5s async delay
  3. Switch to the Fixed tab before the 1.5s delay completes
  4. Switch back to Buggy (or Fixed) — the dropdown shows a persistent white-out overlay
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions