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

Commit 3925231

Browse files
authored
Call focus on input after detecting a tap (#37863)
* fix tap detection and call focus * addressed comments
1 parent 8a40e83 commit 3925231

File tree

2 files changed

+66
-16
lines changed

2 files changed

+66
-16
lines changed

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

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -288,24 +288,24 @@ class TextField extends RoleManager {
288288
_initializeForBlink();
289289
return;
290290
}
291-
num? lastTouchStartOffsetX;
292-
num? lastTouchStartOffsetY;
291+
num? lastPointerDownOffsetX;
292+
num? lastPointerDownOffsetY;
293293

294-
editableElement.addEventListener('touchstart',
294+
editableElement.addEventListener('pointerdown',
295295
allowInterop((DomEvent event) {
296-
final DomTouchEvent touchEvent = event as DomTouchEvent;
297-
lastTouchStartOffsetX = touchEvent.changedTouches!.last.clientX;
298-
lastTouchStartOffsetY = touchEvent.changedTouches!.last.clientY;
296+
final DomPointerEvent pointerEvent = event as DomPointerEvent;
297+
lastPointerDownOffsetX = pointerEvent.clientX;
298+
lastPointerDownOffsetY = pointerEvent.clientY;
299299
}), true);
300300

301301
editableElement.addEventListener(
302-
'touchend', allowInterop((DomEvent event) {
303-
final DomTouchEvent touchEvent = event as DomTouchEvent;
302+
'pointerup', allowInterop((DomEvent event) {
303+
final DomPointerEvent pointerEvent = event as DomPointerEvent;
304304

305-
if (lastTouchStartOffsetX != null) {
306-
assert(lastTouchStartOffsetY != null);
307-
final num offsetX = touchEvent.changedTouches!.last.clientX;
308-
final num offsetY = touchEvent.changedTouches!.last.clientY;
305+
if (lastPointerDownOffsetX != null) {
306+
assert(lastPointerDownOffsetY != null);
307+
final num deltaX = pointerEvent.clientX - lastPointerDownOffsetX!;
308+
final num deltaY = pointerEvent.clientY - lastPointerDownOffsetY!;
309309

310310
// This should match the similar constant defined in:
311311
//
@@ -314,17 +314,30 @@ class TextField extends RoleManager {
314314
// The value is pre-squared so we have to do less math at runtime.
315315
const double kTouchSlop = 18.0 * 18.0; // Logical pixels squared
316316

317-
if (offsetX * offsetX + offsetY * offsetY < kTouchSlop) {
317+
if (deltaX * deltaX + deltaY * deltaY < kTouchSlop) {
318318
// Recognize it as a tap that requires a keyboard.
319319
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
320320
semanticsObject.id, ui.SemanticsAction.tap, null);
321+
322+
// We need to call focus for the following scenario:
323+
// 1. The virtial keyboard in iOS gets dismissed by the 'Done' button
324+
// located at the top right of the keyboard.
325+
// 2. The user tries to focus on the input field again, either by
326+
// VoiceOver or manually, but the keyboard does not show up.
327+
//
328+
// In this scenario, the Flutter framework does not send a semantic update,
329+
// so we need to call focus after detecting a tap to make sure that the
330+
// virtual keyboard will show.
331+
if (semanticsObject.hasFocus) {
332+
editableElement.focus();
333+
}
321334
}
322335
} else {
323-
assert(lastTouchStartOffsetY == null);
336+
assert(lastPointerDownOffsetY == null);
324337
}
325338

326-
lastTouchStartOffsetX = null;
327-
lastTouchStartOffsetY = null;
339+
lastPointerDownOffsetX = null;
340+
lastPointerDownOffsetY = null;
328341
}), true);
329342
}
330343

lib/web_ui/test/engine/semantics/text_field_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,43 @@ void testMain() {
6363
semantics().semanticsEnabled = false;
6464
});
6565

66+
test('tap detection works', () async {
67+
debugBrowserEngineOverride = BrowserEngine.webkit;
68+
debugOperatingSystemOverride = OperatingSystem.iOs;
69+
70+
final SemanticsActionLogger logger = SemanticsActionLogger();
71+
semantics()
72+
..debugOverrideTimestampFunction(() => _testTime)
73+
..semanticsEnabled = true;
74+
75+
createTextFieldSemantics(value: 'hello');
76+
77+
final DomElement textField = appHostNode
78+
.querySelector('input[data-semantics-role="text-field"]')!;
79+
80+
textField.dispatchEvent(createDomPointerEvent(
81+
'pointerdown',
82+
<Object?, Object?>{
83+
'clientX': 25,
84+
'clientY': 48,
85+
},
86+
));
87+
textField.dispatchEvent(createDomPointerEvent(
88+
'pointerup',
89+
<Object?, Object?>{
90+
'clientX': 26,
91+
'clientY': 48,
92+
},
93+
));
94+
95+
expect(await logger.idLog.first, 0);
96+
expect(await logger.actionLog.first, ui.SemanticsAction.tap);
97+
98+
semantics().semanticsEnabled = false;
99+
debugBrowserEngineOverride = null;
100+
debugOperatingSystemOverride = null;
101+
});
102+
66103
// TODO(yjbanov): this test will need to be adjusted for Safari when we add
67104
// Safari testing.
68105
test('sends a tap action when browser requests focus', () async {

0 commit comments

Comments
 (0)