From e8991b3ebfc10da9f978bdd506f67cba5c6c877b Mon Sep 17 00:00:00 2001 From: Karoline Tufte Lien Date: Thu, 28 May 2026 15:20:53 +0200 Subject: [PATCH] fix: [DHIS2-21286] scroll first failed field clear of sticky ScopeSelector Two bugs caused validation-failure scroll to land in the wrong place: 1. withGotoInterface wrapped its class in forwardRef during the React 18 migration (09ed2839e). The outer ref then pointed at the inner field component, not GotoFieldInterface, so .goto() silently no-op'd on every D2Form section field. Drop the wrapper - no consumer reads the forwarded ref. 2. Both goto() implementations did scrollIntoView() then post-corrected with window.scroll(0, scrolledY - 48). The guard skipped the correction whenever scrolledY === 0 (e.g. OccurredAt near top of doc), leaving the field flush with the viewport top - and behind the sticky ScopeSelector bar. Use scrollIntoView({block:'start'}) + scrollMarginTop:80px on the wrapper so the browser lands the field 80px below the viewport top regardless of position. AI Assisted Co-Authored-By: Claude Opus 4.7 (1M context) --- .../internal/DataEntryField.component.tsx | 8 ++----- .../FormFields/New/HOC/withGotoInterface.tsx | 24 +++++-------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntry/dataEntryField/internal/DataEntryField.component.tsx b/src/core_modules/capture-core/components/DataEntry/dataEntryField/internal/DataEntryField.component.tsx index f9b8e9e624..ec5933ad19 100644 --- a/src/core_modules/capture-core/components/DataEntry/dataEntryField/internal/DataEntryField.component.tsx +++ b/src/core_modules/capture-core/components/DataEntry/dataEntryField/internal/DataEntryField.component.tsx @@ -19,12 +19,7 @@ class DataEntryFieldPlain extends React.Component { goto() { if (this.gotoInstance) { - this.gotoInstance.scrollIntoView(); - - const scrolledY = window.scrollY; - if (scrolledY) { - window.scroll(0, scrolledY - 48); - } + this.gotoInstance.scrollIntoView({ block: 'start' }); } } @@ -60,6 +55,7 @@ class DataEntryFieldPlain extends React.Component { ref={(gotoInstance) => { this.gotoInstance = gotoInstance; }} key={propName} data-test={`dataentry-field-${propName}`} + style={{ scrollMarginTop: '80px' }} > - (InnerComponent: React.ComponentType) => { + (InnerComponent: React.ComponentType) => class GotoFieldInterface extends React.Component { gotoInstance: any; goto() { if (this.gotoInstance) { - this.gotoInstance.scrollIntoView(); - - const scrolledY = window.scrollY; - if (scrolledY) { - // TODO: Set the modifier some other way (caused be the fixed header) - window.scroll(0, scrolledY - 48); - } + this.gotoInstance.scrollIntoView({ block: 'start' }); } } render() { - const { forwardedRef, ...rest } = this.props; return (
{ this.gotoInstance = gotoInstance; }} + style={{ scrollMarginTop: '80px' }} >
); } - } - - return forwardRef((props, ref) => ( - - )); - }; + };