diff --git a/mesop/examples/__init__.py b/mesop/examples/__init__.py index 33bebd938..7b970b54c 100644 --- a/mesop/examples/__init__.py +++ b/mesop/examples/__init__.py @@ -38,6 +38,9 @@ from mesop.examples import readme_app as readme_app from mesop.examples import responsive_layout as responsive_layout from mesop.examples import scroll_into_view as scroll_into_view +from mesop.examples import ( + scroll_into_view_deferred as scroll_into_view_deferred, +) from mesop.examples import starter_kit as starter_kit from mesop.examples import sxs as sxs from mesop.examples import testing as testing diff --git a/mesop/examples/scroll_into_view_deferred.py b/mesop/examples/scroll_into_view_deferred.py new file mode 100644 index 000000000..6873e7704 --- /dev/null +++ b/mesop/examples/scroll_into_view_deferred.py @@ -0,0 +1,22 @@ +import mesop as me + + +@me.stateclass +class State: + show_bottom_line: bool = False + + +def on_load(e: me.LoadEvent): + me.scroll_into_view(key="bottom_line") + state = me.state(State) + state.show_bottom_line = True + + +@me.page(path="/scroll_into_view_deferred", on_load=on_load) +def app(): + me.text("Scroll into view deferred") + for _ in range(100): + me.text("filler line") + state = me.state(State) + if state.show_bottom_line: + me.text("bottom line", key="bottom_line") diff --git a/mesop/tests/e2e/scroll_into_view_test.ts b/mesop/tests/e2e/scroll_into_view_test.ts index a38430be8..c674de962 100644 --- a/mesop/tests/e2e/scroll_into_view_test.ts +++ b/mesop/tests/e2e/scroll_into_view_test.ts @@ -12,3 +12,11 @@ test('scroll_into_view', async ({page}) => { await expect(page.getByText('bottom_line')).toBeInViewport(); }); + +test('scroll_into_view - works with components rendered in same tick', async ({ + page, +}) => { + await page.setViewportSize({width: 200, height: 200}); + await page.goto('/scroll_into_view_deferred'); + await expect(page.getByText('bottom line')).toBeInViewport(); +}); diff --git a/mesop/web/src/shell/shell.ts b/mesop/web/src/shell/shell.ts index 7d79f9595..62546951b 100644 --- a/mesop/web/src/shell/shell.ts +++ b/mesop/web/src/shell/shell.ts @@ -130,27 +130,31 @@ export class Shell { } else if (command.hasScrollIntoView()) { // Scroll into view const key = command.getScrollIntoView()!.getKey(); - const targetElements = document.querySelectorAll( - `[data-key="${key}"]`, - ); - if (!targetElements.length) { - console.error( - `Could not scroll to component with key ${key} because no component found`, + // Schedule scroll into view to run after the current event loop tick + // so that the component has time to render. + setTimeout(() => { + const targetElements = document.querySelectorAll( + `[data-key="${key}"]`, ); - return; - } - if (targetElements.length > 1) { - console.warn( - 'Found multiple components', - targetElements, - 'to potentially scroll to for key', - key, - '. This is probably a bug and you should use a unique key identifier.', - ); - } - targetElements[0].parentElement!.scrollIntoView({ - behavior: 'smooth', - }); + if (!targetElements.length) { + console.error( + `Could not scroll to component with key ${key} because no component found`, + ); + return; + } + if (targetElements.length > 1) { + console.warn( + 'Found multiple components', + targetElements, + 'to potentially scroll to for key', + key, + '. This is probably a bug and you should use a unique key identifier.', + ); + } + targetElements[0].parentElement!.scrollIntoView({ + behavior: 'smooth', + }); + }, 0); } else if (command.hasFocusComponent()) { // Focus on component const key = command.getFocusComponent()!.getKey();