This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
[web] Transfer focus to view rootElement on blur/remove. #55045
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e14f998
Add a safelyBlurElement util.
tugorez f43f3bb
Add documentation, extract _transferFocusToViewRoot to an external me…
ditman 1e26ef4
Check if element.contains(activeElement) also.
ditman 72a0f20
Fix tests.
ditman 8124568
Merge branch 'main' into semantics
ditman 93a6ee6
Remove unused import.
ditman 3f19011
Polish docs.
ditman 6651aca
Address PR comments.
ditman 77d7e39
Merge branch 'main' into semantics
ditman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -108,9 +108,86 @@ class FlutterViewManager { | |
const String viewRootSelector = | ||
'${DomManager.flutterViewTagName}[${GlobalHtmlAttributes.flutterViewIdAttributeName}]'; | ||
final DomElement? viewRoot = element?.closest(viewRootSelector); | ||
final String? viewIdAttribute = viewRoot?.getAttribute(GlobalHtmlAttributes.flutterViewIdAttributeName); | ||
final int? viewId = viewIdAttribute == null ? null : int.parse(viewIdAttribute); | ||
return viewId == null ? null : _viewData[viewId]; | ||
if (viewRoot == null) { | ||
// `element` is not inside any flutter view. | ||
return null; | ||
} | ||
|
||
final String? viewIdAttribute = viewRoot.getAttribute(GlobalHtmlAttributes.flutterViewIdAttributeName); | ||
assert(viewIdAttribute != null, 'Located Flutter view is missing its id attribute.'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the additional checks ✅ |
||
|
||
final int? viewId = int.tryParse(viewIdAttribute!); | ||
assert(viewId != null, 'Flutter view id must be a valid int.'); | ||
|
||
return _viewData[viewId]; | ||
} | ||
|
||
/// Blurs [element] by transferring its focus to its [EngineFlutterView] | ||
/// `rootElement`. | ||
/// | ||
/// This operation is asynchronous, but happens as soon as possible | ||
/// (see [Timer.run]). | ||
Future<void> safeBlur(DomElement element) { | ||
return Future<void>(() { | ||
_transferFocusToViewRoot(element); | ||
}); | ||
} | ||
|
||
/// Removes [element] after transferring its focus to its [EngineFlutterView] | ||
/// `rootElement`. | ||
/// | ||
/// This operation is asynchronous, but happens as soon as possible | ||
/// (see [Timer.run]). | ||
/// | ||
/// There's a synchronous version of this method: [safeRemoveSync]. | ||
Future<void> safeRemove(DomElement element) { | ||
return Future<void>(() => safeRemoveSync(element)); | ||
} | ||
|
||
/// Synchronously removes [element] after transferring its focus to its | ||
/// [EngineFlutterView] `rootElement`. | ||
/// | ||
/// This is the synchronous version of [safeRemove]. | ||
void safeRemoveSync(DomElement element) { | ||
_transferFocusToViewRoot(element, removeElement: true); | ||
} | ||
|
||
/// Blurs (removes focus) from [element] by transferring its focus to its | ||
/// [EngineFlutterView] DOM's `rootElement` before (optionally) removing it. | ||
/// | ||
/// By default, blurring a focused `element` (or removing it from the DOM) | ||
/// transfers its focus to the `body` element of the page. | ||
/// | ||
/// This method achieves two things when blurring/removing `element`: | ||
/// | ||
/// * Ensures the focus is preserved within the Flutter View when blurring | ||
/// elements that are part of the internal DOM structure of the Flutter | ||
/// app. | ||
/// * Prevents the Flutter engine from reporting bogus "blur" events from the | ||
/// Flutter View. | ||
/// | ||
/// When [removeElement] is true, `element` will be removed from the DOM after | ||
/// its focus (or that of any of its children) is transferred to the root of | ||
/// the view. | ||
/// | ||
/// See: https://jsfiddle.net/ditman/1e2swpno for a JS focus transfer demo. | ||
void _transferFocusToViewRoot( | ||
DomElement element, { | ||
bool removeElement = false, | ||
}) { | ||
final DomElement? activeElement = domDocument.activeElement; | ||
// If the element we're operating on is not active anymore (it can happen | ||
// when this method is called asynchronously), OR the element that we want | ||
// to remove *contains* the `activeElement`. | ||
if (element == activeElement || removeElement && element.contains(activeElement)) { | ||
// Transfer the browser focus to the `rootElement` of the | ||
// [EngineFlutterView] that contains `element` | ||
final EngineFlutterView? view = findViewForElement(element); | ||
view?.dom.rootElement.focusWithoutScroll(); | ||
} | ||
if (removeElement) { | ||
element.remove(); | ||
} | ||
} | ||
|
||
void dispose() { | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some roles will remove their DOM children that may have focus in their implementation of
dispose()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see any test break. Is this a behavior that can/will be added later? Do I need to change other classes as well?
Apologies, I have zero experience with semantics.