@@ -108,37 +108,72 @@ class FlutterViewManager {
108108 const String viewRootSelector =
109109 '${DomManager .flutterViewTagName }[${GlobalHtmlAttributes .flutterViewIdAttributeName }]' ;
110110 final DomElement ? viewRoot = element? .closest (viewRootSelector);
111- final String ? viewIdAttribute = viewRoot? .getAttribute (GlobalHtmlAttributes .flutterViewIdAttributeName);
112- final int ? viewId = viewIdAttribute == null ? null : int .parse (viewIdAttribute);
113- return viewId == null ? null : _viewData[viewId];
111+ if (viewRoot == null ) {
112+ // `element` is not inside any flutter view.
113+ return null ;
114+ }
115+
116+ final String ? viewIdAttribute = viewRoot.getAttribute (GlobalHtmlAttributes .flutterViewIdAttributeName);
117+ assert (viewIdAttribute != null , 'Located Flutter view is missing its id attribute.' );
118+
119+ final int ? viewId = int .tryParse (viewIdAttribute! );
120+ assert (viewId != null , 'Flutter view id must be a valid int.' );
121+
122+ return _viewData[viewId];
114123 }
115124
116- /// Safely manages focus when blurring and optionally removing a DOM element.
125+ /// Attempts to transfer focus (blur) from [element] to its
126+ /// [EngineFlutterView] DOM's `rootElement` .
127+ ///
128+ /// This focus "transfer" achieves two things:
129+ ///
130+ /// * Ensures the focus is preserved within the Flutter View when blurring
131+ /// elements that are part of the internal DOM structure of the Flutter
132+ /// app. This...
133+ /// * Prevents the Flutter engine from reporting bogus "blur" events from the
134+ /// Flutter View because, by default, calling "blur" on an element moves the
135+ /// document.currentElement to the `body` of the page.
117136 ///
118- /// This function ensures the blur operation doesn't disrupt the framework's view focus management .
137+ /// See: https://jsfiddle.net/ditman/1e2swpno for a JS-only demonstration .
119138 ///
120- /// * [removeElement] controls whether the element is removed from the DOM after being blurred.
121- /// * [delayed] controls whether the engine will be given the opportunity to focus on another element first.
122- void safelyBlurElement (DomElement element, {bool removeElement = false , bool delayed = true }) {
123- final EngineFlutterView ? view = findViewForElement (element);
124-
125- void blur () {
126- // If by the time the timer fired the focused element is no longer the
127- // editing element whose editing session was disabled, there's no need to
128- // move the focus, as it is likely that another widget already took the
129- // focus.
130- if (element == domDocument.activeElement) {
131- view? .dom.rootElement.focusWithoutScroll ();
132- }
133- if (removeElement) {
134- element.remove ();
135- }
139+ /// When [removeElement] is true, `element` will be removed from the DOM after
140+ /// its focus is transferred to the root of the view. This can be used to
141+ /// safely remove (potentially focused) element, by preserving focus within
142+ /// the Flutter view.
143+ ///
144+ /// When [delayed] is true, the blur operation is executed asynchronously as
145+ /// soon as possible (see [Timer.run] ). Else it runs immediately.
146+ void safelyBlurElement (
147+ DomElement element, {
148+ bool removeElement = false ,
149+ bool delayed = true
150+ }) {
151+ if (delayed) {
152+ Timer .run (() {
153+ _transferFocusToViewRoot (element, removeElement: removeElement);
154+ });
155+ return ;
136156 }
157+ _transferFocusToViewRoot (element, removeElement: removeElement);
158+ }
137159
138- if (delayed) {
139- Timer (Duration .zero, blur);
140- } else {
141- blur ();
160+ // The actual implementation of [safelyBlurElement].
161+ void _transferFocusToViewRoot (
162+ DomElement element, {
163+ required bool removeElement
164+ }) {
165+ // If by the time this method is called the focused element is no longer
166+ // `element`, there's no need to move the focus.
167+ //
168+ // This can happen when another element grabs focus when this method runs
169+ // "delayed".
170+ if (element == domDocument.activeElement) {
171+ final EngineFlutterView ? view = findViewForElement (element);
172+ // Transfer the browser focus to the root element of `view`
173+ view? .dom.rootElement.focusWithoutScroll ();
174+ }
175+ if (removeElement) {
176+ element.remove ();
142177 }
143178 }
144179
0 commit comments