2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
4
5
+ import 'dart:async' ;
5
6
import 'package:ui/src/engine.dart' ;
6
7
import 'package:ui/ui.dart' as ui;
7
8
8
9
/// Tracks the [FlutterView] s focus changes.
9
10
final class ViewFocusBinding {
10
- /// Creates a [ViewFocusBinding] instance.
11
- ViewFocusBinding ._();
11
+ ViewFocusBinding (this ._viewManager, this ._onViewFocusChange);
12
12
13
- /// The [ViewFocusBinding] singleton.
14
- static final ViewFocusBinding instance = ViewFocusBinding ._() ;
13
+ final FlutterViewManager _viewManager;
14
+ final ui. ViewFocusChangeCallback _onViewFocusChange ;
15
15
16
- final List <ui.ViewFocusChangeCallback > _listeners = < ui.ViewFocusChangeCallback > [];
17
16
int ? _lastViewId;
18
17
ui.ViewFocusDirection _viewFocusDirection = ui.ViewFocusDirection .forward;
19
18
20
- /// Subscribes the [listener] to [ui.ViewFocusEvent] events.
21
- void addListener (ui.ViewFocusChangeCallback listener) {
22
- if (_listeners.isEmpty) {
23
- domDocument.body? .addEventListener (_keyDown, _handleKeyDown);
24
- domDocument.body? .addEventListener (_keyUp, _handleKeyUp);
25
- domDocument.body? .addEventListener (_focusin, _handleFocusin);
26
- domDocument.body? .addEventListener (_focusout, _handleFocusout);
27
- }
28
- _listeners.add (listener);
29
- }
19
+ StreamSubscription <int >? _onViewCreatedListener;
30
20
31
- /// Removes the [listener] from the [ui.ViewFocusEvent] events subscription.
32
- void removeListener (ui.ViewFocusChangeCallback listener) {
33
- _listeners.remove (listener);
34
- if (_listeners.isEmpty) {
35
- domDocument.body? .removeEventListener (_keyDown, _handleKeyDown);
36
- domDocument.body? .removeEventListener (_keyUp, _handleKeyUp);
37
- domDocument.body? .removeEventListener (_focusin, _handleFocusin);
38
- domDocument.body? .removeEventListener (_focusout, _handleFocusout);
39
- }
21
+ void init () {
22
+ domDocument.body? .addEventListener (_keyDown, _handleKeyDown);
23
+ domDocument.body? .addEventListener (_keyUp, _handleKeyUp);
24
+ domDocument.body? .addEventListener (_focusin, _handleFocusin);
25
+ domDocument.body? .addEventListener (_focusout, _handleFocusout);
26
+ _onViewCreatedListener = _viewManager.onViewCreated.listen (_handleViewCreated);
40
27
}
41
28
42
- void _notify (ui.ViewFocusEvent event) {
43
- for (final ui.ViewFocusChangeCallback listener in _listeners) {
44
- listener (event);
45
- }
29
+ void dispose () {
30
+ domDocument.body? .removeEventListener (_keyDown, _handleKeyDown);
31
+ domDocument.body? .removeEventListener (_keyUp, _handleKeyUp);
32
+ domDocument.body? .removeEventListener (_focusin, _handleFocusin);
33
+ domDocument.body? .removeEventListener (_focusout, _handleFocusout);
34
+ _onViewCreatedListener? .cancel ();
46
35
}
47
36
48
37
late final DomEventListener _handleFocusin = createDomEventListener ((DomEvent event) {
@@ -71,6 +60,7 @@ final class ViewFocusBinding {
71
60
if (viewId == _lastViewId) {
72
61
return ;
73
62
}
63
+
74
64
final ui.ViewFocusEvent event;
75
65
if (viewId == null ) {
76
66
event = ui.ViewFocusEvent (
@@ -85,18 +75,44 @@ final class ViewFocusBinding {
85
75
direction: _viewFocusDirection,
86
76
);
87
77
}
78
+ _maybeMarkViewAsFocusable (_lastViewId, reachableByKeyboard: true );
79
+ _maybeMarkViewAsFocusable (viewId, reachableByKeyboard: false );
88
80
_lastViewId = viewId;
89
- _notify (event);
81
+ _onViewFocusChange (event);
82
+ }
83
+
84
+ int ? _viewId (DomElement ? element) {
85
+ final DomElement ? rootElement = element? .closest (DomManager .flutterViewTagName);
86
+ if (rootElement == null ) {
87
+ return null ;
88
+ }
89
+ return _viewManager.viewIdForRootElement (rootElement);
90
90
}
91
91
92
- static int ? _viewId (DomElement ? element) {
93
- final DomElement ? viewElement = element? .closest (
94
- DomManager .flutterViewTagName,
95
- );
96
- final String ? viewIdAttribute = viewElement? .getAttribute (
97
- GlobalHtmlAttributes .flutterViewIdAttributeName,
98
- );
99
- return viewIdAttribute == null ? null : int .tryParse (viewIdAttribute);
92
+ void _handleViewCreated (int viewId) {
93
+ _maybeMarkViewAsFocusable (viewId, reachableByKeyboard: true );
94
+ }
95
+
96
+ void _maybeMarkViewAsFocusable (
97
+ int ? viewId, {
98
+ required bool reachableByKeyboard,
99
+ }) {
100
+ if (viewId == null ) {
101
+ return ;
102
+ }
103
+
104
+ final DomElement ? rootElement = _viewManager[viewId]? .dom.rootElement;
105
+ if (EngineSemantics .instance.semanticsEnabled) {
106
+ rootElement? .removeAttribute ('tabindex' );
107
+ } else {
108
+ // A tabindex with value zero means the DOM element can be reached by using
109
+ // the keyboard (tab, shift + tab). When its value is -1 it is still focusable
110
+ // but can't be focused by the result of keyboard events This is specially
111
+ // important when the semantics tree is enabled as it puts DOM nodes inside
112
+ // the flutter view and having it with a zero tabindex messes the focus
113
+ // traversal order when pressing tab or shift tab.
114
+ rootElement? .setAttribute ('tabindex' , reachableByKeyboard ? 0 : - 1 );
115
+ }
100
116
}
101
117
102
118
static const String _focusin = 'focusin' ;
0 commit comments