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

Commit 63c2403

Browse files
authored
[web] Reland "Fix focus management for text fields (#51009)" (#53537)
Relands: [**Fix focus management for text fields** (#51009)](#51009) by: * Reverting commit: cf3ac2d (#53502). * Keeping the new `ViewFocusBinding` disabled, as [suggested](#51009 (comment)). [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 1fb20a2 commit 63c2403

File tree

6 files changed

+250
-253
lines changed

6 files changed

+250
-253
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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)