Skip to content

Commit 7eeb697

Browse files
authored
Add view focus direction detection to flutter web. (flutter#50843)
Add view focus direction detection to flutter web. Relevant Issues are: * Design doc: https://flutter.dev/go/focus-management * Focus in web multiview: flutter#137443 * Platform dispatcher changes: flutter/engine#49841 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent be9a8e5 commit 7eeb697

File tree

2 files changed

+62
-15
lines changed

2 files changed

+62
-15
lines changed

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

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ final class ViewFocusBinding {
1414
static final ViewFocusBinding instance = ViewFocusBinding._();
1515

1616
final List<ui.ViewFocusChangeCallback> _listeners = <ui.ViewFocusChangeCallback>[];
17+
int? _lastViewId;
18+
ui.ViewFocusDirection _viewFocusDirection = ui.ViewFocusDirection.forward;
1719

1820
/// Subscribes the [listener] to [ui.ViewFocusEvent] events.
1921
void addListener(ui.ViewFocusChangeCallback listener) {
2022
if (_listeners.isEmpty) {
21-
domDocument.body?.addEventListener(_focusin, _handleFocusin, true);
22-
domDocument.body?.addEventListener(_focusout, _handleFocusout, true);
23+
domDocument.body?.addEventListener(_keyDown, _handleKeyDown);
24+
domDocument.body?.addEventListener(_keyUp, _handleKeyUp);
25+
domDocument.body?.addEventListener(_focusin, _handleFocusin);
26+
domDocument.body?.addEventListener(_focusout, _handleFocusout);
2327
}
2428
_listeners.add(listener);
2529
}
@@ -28,8 +32,10 @@ final class ViewFocusBinding {
2832
void removeListener(ui.ViewFocusChangeCallback listener) {
2933
_listeners.remove(listener);
3034
if (_listeners.isEmpty) {
31-
domDocument.body?.removeEventListener(_focusin, _handleFocusin, true);
32-
domDocument.body?.removeEventListener(_focusout, _handleFocusout, true);
35+
domDocument.body?.removeEventListener(_keyDown, _handleKeyDown);
36+
domDocument.body?.removeEventListener(_keyUp, _handleKeyUp);
37+
domDocument.body?.removeEventListener(_focusin, _handleFocusin);
38+
domDocument.body?.removeEventListener(_focusout, _handleFocusout);
3339
}
3440
}
3541

@@ -39,21 +45,32 @@ final class ViewFocusBinding {
3945
}
4046
}
4147

42-
late final DomEventListener _handleFocusin = createDomEventListener(
43-
(DomEvent event) => _handleFocusChange(event.target as DomElement?),
44-
);
48+
late final DomEventListener _handleFocusin = createDomEventListener((DomEvent event) {
49+
event as DomFocusEvent;
50+
_handleFocusChange(event.target as DomElement?);
51+
});
4552

46-
late final DomEventListener _handleFocusout = createDomEventListener(
47-
(DomEvent event) => _handleFocusChange((event as DomFocusEvent).relatedTarget as DomElement?),
48-
);
53+
late final DomEventListener _handleFocusout = createDomEventListener((DomEvent event) {
54+
event as DomFocusEvent;
55+
_handleFocusChange(event.relatedTarget as DomElement?);
56+
});
57+
58+
late final DomEventListener _handleKeyDown = createDomEventListener((DomEvent event) {
59+
event as DomKeyboardEvent;
60+
if (event.shiftKey) {
61+
_viewFocusDirection = ui.ViewFocusDirection.backward;
62+
}
63+
});
64+
65+
late final DomEventListener _handleKeyUp = createDomEventListener((DomEvent event) {
66+
_viewFocusDirection = ui.ViewFocusDirection.forward;
67+
});
4968

50-
int? _lastViewId;
5169
void _handleFocusChange(DomElement? focusedElement) {
5270
final int? viewId = _viewId(focusedElement);
5371
if (viewId == _lastViewId) {
5472
return;
5573
}
56-
5774
final ui.ViewFocusEvent event;
5875
if (viewId == null) {
5976
event = ui.ViewFocusEvent(
@@ -65,7 +82,7 @@ final class ViewFocusBinding {
6582
event = ui.ViewFocusEvent(
6683
viewId: viewId,
6784
state: ui.ViewFocusState.focused,
68-
direction: ui.ViewFocusDirection.forward,
85+
direction: _viewFocusDirection,
6986
);
7087
}
7188
_lastViewId = viewId;
@@ -84,4 +101,6 @@ final class ViewFocusBinding {
84101

85102
static const String _focusin = 'focusin';
86103
static const String _focusout = 'focusout';
104+
static const String _keyDown = 'keydown';
105+
static const String _keyUp = 'keyup';
87106
}

lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
54
import 'package:test/bootstrap/browser.dart';
65
import 'package:test/test.dart';
76
import 'package:ui/src/engine.dart';
@@ -85,8 +84,13 @@ void testMain() {
8584

8685
focusableViewElement1.focus();
8786
focusableViewElement2.focus();
87+
// The statements simulate the user pressing shift + tab in the keyboard.
88+
// Synthetic keyboard events do not trigger focus changes.
89+
domDocument.body!.pressTabKey(shift: true);
90+
focusableViewElement1.focus();
91+
domDocument.body!.releaseTabKey();
8892

89-
expect(viewFocusEvents, hasLength(2));
93+
expect(viewFocusEvents, hasLength(3));
9094

9195
expect(viewFocusEvents[0].viewId, view1.viewId);
9296
expect(viewFocusEvents[0].state, ui.ViewFocusState.focused);
@@ -95,6 +99,10 @@ void testMain() {
9599
expect(viewFocusEvents[1].viewId, view2.viewId);
96100
expect(viewFocusEvents[1].state, ui.ViewFocusState.focused);
97101
expect(viewFocusEvents[1].direction, ui.ViewFocusDirection.forward);
102+
103+
expect(viewFocusEvents[2].viewId, view1.viewId);
104+
expect(viewFocusEvents[2].state, ui.ViewFocusState.focused);
105+
expect(viewFocusEvents[2].direction, ui.ViewFocusDirection.backward);
98106
});
99107

100108
test('fires a focus event - focus transitions on and off views', () async {
@@ -137,3 +145,23 @@ void testMain() {
137145
});
138146
});
139147
}
148+
149+
extension on DomElement {
150+
void pressTabKey({bool shift = false}) {
151+
dispatchKeyboardEvent(type: 'keydown', key: 'Tab', shiftKey: shift);
152+
}
153+
154+
void releaseTabKey({bool shift = false}) {
155+
dispatchKeyboardEvent(type: 'keyup', key: 'Tab', shiftKey: shift);
156+
}
157+
158+
void dispatchKeyboardEvent({
159+
required String type,
160+
required String key,
161+
bool shiftKey = false,
162+
}) {
163+
dispatchEvent(
164+
createDomKeyboardEvent(type, <String, Object>{'key': key, 'shiftKey': shiftKey}),
165+
);
166+
}
167+
}

0 commit comments

Comments
 (0)