Skip to content

Commit e16e58a

Browse files
yjbanovfilmil
authored andcommitted
fix editable placement in a11y mode (flutter#14581)
1 parent b1d4068 commit e16e58a

4 files changed

Lines changed: 443 additions & 282 deletions

File tree

lib/web_ui/lib/src/engine/semantics/text_field.dart

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,70 @@
44

55
part of engine;
66

7+
/// Text editing used by accesibility mode.
8+
///
9+
/// [SemanticsTextEditingStrategy] assumes the caller will own the creation,
10+
/// insertion and disposal of the DOM element. Due to this
11+
/// [initializeElementPlacement], [initializeTextEditing] and
12+
/// [disable] strategies are handled differently.
13+
///
14+
/// This class is still responsible for hooking up the DOM element with the
15+
/// [HybridTextEditing] instance so that changes are communicated to Flutter.
16+
class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy {
17+
/// Creates a [SemanticsTextEditingStrategy] that eagerly instantiates
18+
/// [domElement] so the caller can insert it before calling
19+
/// [SemanticsTextEditingStrategy.enable].
20+
SemanticsTextEditingStrategy(
21+
HybridTextEditing owner, html.HtmlElement domElement)
22+
: super(owner) {
23+
// Make sure the DOM element is of a type that we support for text editing.
24+
// TODO(yjbanov): move into initializer list when https://github.com/dart-lang/sdk/issues/37881 is fixed.
25+
assert((domElement is html.InputElement) ||
26+
(domElement is html.TextAreaElement));
27+
super.domElement = domElement;
28+
}
29+
30+
@override
31+
void disable() {
32+
// We don't want to remove the DOM element because the caller is responsible
33+
// for that.
34+
//
35+
// Remove focus from the editable element to cause the keyboard to hide.
36+
// Otherwise, the keyboard stays on screen even when the user navigates to
37+
// a different screen (e.g. by hitting the "back" button).
38+
domElement.blur();
39+
}
40+
41+
@override
42+
void initializeElementPlacement() {
43+
// Element placement is done by [TextField].
44+
}
45+
46+
@override
47+
void initializeTextEditing(InputConfiguration inputConfig,
48+
{_OnChangeCallback onChange, _OnActionCallback onAction}) {
49+
// In accesibilty mode, the user of this class is supposed to insert the
50+
// [domElement] on their own. Let's make sure they did.
51+
assert(domElement != null);
52+
assert(html.document.body.contains(domElement));
53+
54+
isEnabled = true;
55+
_inputConfiguration = inputConfig;
56+
_onChange = onChange;
57+
_onAction = onAction;
58+
59+
domElement.focus();
60+
}
61+
62+
@override
63+
void setEditingState(EditingState editingState) {
64+
super.setEditingState(editingState);
65+
66+
// Refocus after setting editing state.
67+
domElement.focus();
68+
}
69+
}
70+
771
/// Manages semantics objects that represent editable text fields.
872
///
973
/// This role is implemented via a content-editable HTML element. This role does
@@ -19,15 +83,15 @@ class TextField extends RoleManager {
1983
semanticsObject.hasFlag(ui.SemanticsFlag.isMultiline)
2084
? html.TextAreaElement()
2185
: html.InputElement();
22-
persistentTextEditingElement = PersistentTextEditingElement(
86+
textEditingElement = SemanticsTextEditingStrategy(
2387
textEditing,
2488
editableDomElement,
2589
);
2690
_setupDomElement();
2791
}
2892

29-
PersistentTextEditingElement persistentTextEditingElement;
30-
html.Element get _textFieldElement => persistentTextEditingElement.domElement;
93+
SemanticsTextEditingStrategy textEditingElement;
94+
html.Element get _textFieldElement => textEditingElement.domElement;
3195

3296
void _setupDomElement() {
3397
// On iOS, even though the semantic text field is transparent, the cursor
@@ -61,6 +125,7 @@ class TextField extends RoleManager {
61125
switch (browserEngine) {
62126
case BrowserEngine.blink:
63127
case BrowserEngine.edge:
128+
case BrowserEngine.ie11:
64129
case BrowserEngine.firefox:
65130
case BrowserEngine.ie11:
66131
case BrowserEngine.unknown:
@@ -82,7 +147,7 @@ class TextField extends RoleManager {
82147
return;
83148
}
84149

85-
textEditing.useCustomEditableElement(persistentTextEditingElement);
150+
textEditing.useCustomEditableElement(textEditingElement);
86151
ui.window
87152
.onSemanticsAction(semanticsObject.id, ui.SemanticsAction.tap, null);
88153
});
@@ -98,7 +163,7 @@ class TextField extends RoleManager {
98163
num lastTouchStartOffsetY;
99164

100165
_textFieldElement.addEventListener('touchstart', (html.Event event) {
101-
textEditing.useCustomEditableElement(persistentTextEditingElement);
166+
textEditing.useCustomEditableElement(textEditingElement);
102167
final html.TouchEvent touchEvent = event;
103168
lastTouchStartOffsetX = touchEvent.changedTouches.last.client.x;
104169
lastTouchStartOffsetY = touchEvent.changedTouches.last.client.y;

0 commit comments

Comments
 (0)