Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 87a632b

Browse files
authored
Fix focus management for text fields (#51009)
Fix focus management for text fields This PR: 1. Refactors the DOM `focus` function to take [options][1] 2. Removes the timers sorrounding the `activeDomElement.focus()` so that the input elements get focused as immediate as possible. 3. Prevents the default on pointerdown in a Flutter View and schedules a `requestViewFocusChange` to claim focus in that view after some time. This gives `2` the opportunity to focus the right `<input />` or `<textarea />` element. This helps focus correctly transition from one input element to another (without jumping to a flutter view in between). 4. Deactivating a `TextField` doesn't blur the focused element anymore, it insteads schedules for later a call to move the focus to the flutter view if nothing inside it claimed focus first. 5. Prevents scroll in all the focus calls (this should help with the view jumping when focusing one text field after another). ## Sample apps 1. Full screen mode: https://tugorez.com/flutter_focus_web 2. Embedded mode: https://tugorez.com/flutter_focus_web?embedded [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 5d97d2b commit 87a632b

File tree

6 files changed

+251
-254
lines changed

6 files changed

+251
-254
lines changed

lib/web_ui/lib/src/engine/dom.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,16 @@ extension DomElementExtension on DomElement {
658658
external JSNumber? get _tabIndex;
659659
double? get tabIndex => _tabIndex?.toDartDouble;
660660

661-
external JSVoid focus();
661+
@JS('focus')
662+
external JSVoid _focus(JSAny options);
663+
664+
void focus({bool? preventScroll, bool? focusVisible}) {
665+
final Map<String, bool> options = <String, bool>{
666+
if (preventScroll != null) 'preventScroll': preventScroll,
667+
if (focusVisible != null) 'focusVisible': focusVisible,
668+
};
669+
_focus(options.toJSAnyDeep);
670+
}
662671

663672
@JS('scrollTop')
664673
external JSNumber get _scrollTop;
@@ -2249,9 +2258,11 @@ extension DomKeyboardEventExtension on DomKeyboardEvent {
22492258
external JSBoolean? get _repeat;
22502259
bool? get repeat => _repeat?.toDart;
22512260

2261+
// Safari injects synthetic keyboard events after auto-complete that don't
2262+
// have a `shiftKey` attribute, so this property must be nullable.
22522263
@JS('shiftKey')
2253-
external JSBoolean get _shiftKey;
2254-
bool get shiftKey => _shiftKey.toDart;
2264+
external JSBoolean? get _shiftKey;
2265+
bool? get shiftKey => _shiftKey?.toDart;
22552266

22562267
@JS('isComposing')
22572268
external JSBoolean get _isComposing;

lib/web_ui/lib/src/engine/keyboard_binding.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ class FlutterHtmlKeyboardEvent {
207207
num? get timeStamp => _event.timeStamp;
208208
bool get altKey => _event.altKey;
209209
bool get ctrlKey => _event.ctrlKey;
210-
bool get shiftKey => _event.shiftKey;
210+
bool get shiftKey => _event.shiftKey ?? false;
211211
bool get metaKey => _event.metaKey;
212212
bool get isComposing => _event.isComposing;
213213

lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ final class ViewFocusBinding {
1616
///
1717
/// DO NOT rely on this bit as it will go away soon. You're warned :)!
1818
@visibleForTesting
19-
static bool isEnabled = false;
19+
static bool isEnabled = true;
2020

2121
final FlutterViewManager _viewManager;
2222
final ui.ViewFocusChangeCallback _onViewFocusChange;
@@ -51,7 +51,7 @@ final class ViewFocusBinding {
5151
if (state == ui.ViewFocusState.focused) {
5252
// Only move the focus to the flutter view if nothing inside it is focused already.
5353
if (viewId != _viewId(domDocument.activeElement)) {
54-
viewElement?.focus();
54+
viewElement?.focus(preventScroll: true);
5555
}
5656
} else {
5757
viewElement?.blur();
@@ -70,7 +70,7 @@ final class ViewFocusBinding {
7070

7171
late final DomEventListener _handleKeyDown = createDomEventListener((DomEvent event) {
7272
event as DomKeyboardEvent;
73-
if (event.shiftKey) {
73+
if (event.shiftKey ?? false) {
7474
_viewFocusDirection = ui.ViewFocusDirection.backward;
7575
}
7676
});

lib/web_ui/lib/src/engine/pointer_binding.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,22 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
982982
);
983983
_convertEventsToPointerData(data: pointerData, event: event, details: down);
984984
_callback(event, pointerData);
985+
986+
if (event.target == _viewTarget) {
987+
// Ensure smooth focus transitions between text fields within the Flutter view.
988+
// Without preventing the default and this delay, the engine may not have fully
989+
// rendered the next input element, leading to the focus incorrectly returning to
990+
// the main Flutter view instead.
991+
// A zero-length timer is sufficient in all tested browsers to achieve this.
992+
event.preventDefault();
993+
Timer(Duration.zero, () {
994+
EnginePlatformDispatcher.instance.requestViewFocusChange(
995+
viewId: _view.viewId,
996+
state: ui.ViewFocusState.focused,
997+
direction: ui.ViewFocusDirection.undefined,
998+
);
999+
});
1000+
}
9851001
});
9861002

9871003
// Why `domWindow` you ask? See this fiddle: https://jsfiddle.net/ditman/7towxaqp

0 commit comments

Comments
 (0)