From 1899b49d252abc6003f763cea8db2a51efa941ec Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 18 Aug 2025 12:59:57 -0700 Subject: [PATCH] fix(refresher): prevent focus-related scroll jumps on refresh (#30636) Issue number: resolves # --------- ## What is the current behavior? Currently, if you focus on something and then refresh with a refresher, the browser will try to scroll to what's focused after refreshing. This can be an unexpected and disrupting user experience. https://github.com/user-attachments/assets/3ef5999d-d104-422a-a6a9-4f478912f48a ## What is the new behavior? With these changes, we blur the active element to prevent the browser from trying to scroll back to something off screen after refreshing. Also, I did try to create regression tests for this, but playwright actually doesn't currently seem to make it possible as far as I can tell - that's actually what I spent most of my time on this issue trying to do. Looks like the only way to trigger the refresher with playwright uses mouse click events, which inherently blurs the active element. You need to use cursor events for this to work, because cursor events do not cause a blur on the active element. https://github.com/user-attachments/assets/bd1a3bfc-9b48-4b3f-b8dc-6959eefc9107 ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information **Current dev build:** ``` 8.7.3-dev.11755285796.12743331 ``` --- core/src/components/refresher/refresher.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index 8d01687308..77f72f1a68 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -253,6 +253,15 @@ export class Refresher implements ComponentInterface { this.didRefresh = true; hapticImpact({ style: ImpactStyle.Light }); + /** + * Clear focus from any active element to prevent scroll jumps + * when the refresh completes + */ + const activeElement = document.activeElement as HTMLElement; + if (activeElement?.blur !== undefined) { + activeElement.blur(); + } + /** * Translate the content element otherwise when pointer is removed * from screen the scroll content will bounce back over the refresher @@ -733,6 +742,16 @@ export class Refresher implements ComponentInterface { // place the content in a hangout position while it thinks this.setCss(this.pullMin, this.snapbackDuration, true, ''); + /** + * Clear focus from any active element to prevent the browser + * from restoring focus and causing scroll jumps after refresh. + * This ensures the view stays at the top after refresh completes. + */ + const activeElement = document.activeElement as HTMLElement; + if (activeElement?.blur !== undefined) { + activeElement.blur(); + } + // emit "refresh" because it was pulled down far enough // and they let go to begin refreshing this.ionRefresh.emit({