@@ -108,37 +108,72 @@ class FlutterViewManager {
108
108
const String viewRootSelector =
109
109
'${DomManager .flutterViewTagName }[${GlobalHtmlAttributes .flutterViewIdAttributeName }]' ;
110
110
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];
114
123
}
115
124
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.
117
136
///
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 .
119
138
///
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 ;
136
156
}
157
+ _transferFocusToViewRoot (element, removeElement: removeElement);
158
+ }
137
159
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 ();
142
177
}
143
178
}
144
179
0 commit comments