diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart
index 481c9c3cbc525..57bc1fa30726d 100644
--- a/lib/ui/semantics.dart
+++ b/lib/ui/semantics.dart
@@ -220,9 +220,6 @@ class SemanticsAction {
/// must immediately become editable, opening a virtual keyboard, if needed.
/// Buttons must respond to tap/click events from the keyboard.
///
- /// Widget reaction to this action must be idempotent. It is possible to
- /// receive this action more than once, or when the widget is already focused.
- ///
/// Focus behavior is specific to the platform and to the assistive technology
/// used. Typically on desktop operating systems, such as Windows, macOS, and
/// Linux, moving accessibility focus will also move the input focus. On
diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart
index 32d100df8f114..b81bba41245f2 100644
--- a/lib/web_ui/lib/src/engine/dom.dart
+++ b/lib/web_ui/lib/src/engine/dom.dart
@@ -2753,30 +2753,6 @@ DomCompositionEvent createDomCompositionEvent(String type,
}
}
-/// This is a pseudo-type for DOM elements that have the boolean `disabled`
-/// property.
-///
-/// This type cannot be part of the actual type hierarchy because each DOM type
-/// defines its `disabled` property ad hoc, without inheriting it from a common
-/// type, e.g. [DomHTMLInputElement] and [DomHTMLTextAreaElement].
-///
-/// To use, simply cast any element known to have the `disabled` property to
-/// this type using `as DomElementWithDisabledProperty`, then read and write
-/// this property as normal.
-@JS()
-@staticInterop
-class DomElementWithDisabledProperty extends DomHTMLElement {}
-
-extension DomElementWithDisabledPropertyExtension on DomElementWithDisabledProperty {
- @JS('disabled')
- external JSBoolean? get _disabled;
- bool? get disabled => _disabled?.toDart;
-
- @JS('disabled')
- external set _disabled(JSBoolean? value);
- set disabled(bool? value) => _disabled = value?.toJS;
-}
-
@JS()
@staticInterop
class DomHTMLInputElement extends DomHTMLElement {}
diff --git a/lib/web_ui/lib/src/engine/semantics/focusable.dart b/lib/web_ui/lib/src/engine/semantics/focusable.dart
index 331e1cd50c061..35fff64a50158 100644
--- a/lib/web_ui/lib/src/engine/semantics/focusable.dart
+++ b/lib/web_ui/lib/src/engine/semantics/focusable.dart
@@ -81,6 +81,9 @@ typedef _FocusTarget = ({
/// The listener for the "focus" DOM event.
DomEventListener domFocusListener,
+
+ /// The listener for the "blur" DOM event.
+ DomEventListener domBlurListener,
});
/// Implements accessibility focus management for arbitrary elements.
@@ -132,6 +135,7 @@ class AccessibilityFocusManager {
semanticsNodeId: semanticsNodeId,
element: previousTarget.element,
domFocusListener: previousTarget.domFocusListener,
+ domBlurListener: previousTarget.domBlurListener,
);
return;
}
@@ -144,12 +148,14 @@ class AccessibilityFocusManager {
final _FocusTarget newTarget = (
semanticsNodeId: semanticsNodeId,
element: element,
- domFocusListener: createDomEventListener((_) => _didReceiveDomFocus()),
+ domFocusListener: createDomEventListener((_) => _setFocusFromDom(true)),
+ domBlurListener: createDomEventListener((_) => _setFocusFromDom(false)),
);
_target = newTarget;
element.tabIndex = 0;
element.addEventListener('focus', newTarget.domFocusListener);
+ element.addEventListener('blur', newTarget.domBlurListener);
}
/// Stops managing the focus of the current element, if any.
@@ -164,9 +170,10 @@ class AccessibilityFocusManager {
}
target.element.removeEventListener('focus', target.domFocusListener);
+ target.element.removeEventListener('blur', target.domBlurListener);
}
- void _didReceiveDomFocus() {
+ void _setFocusFromDom(bool acquireFocus) {
final _FocusTarget? target = _target;
if (target == null) {
@@ -177,7 +184,9 @@ class AccessibilityFocusManager {
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
target.semanticsNodeId,
- ui.SemanticsAction.focus,
+ acquireFocus
+ ? ui.SemanticsAction.didGainAccessibilityFocus
+ : ui.SemanticsAction.didLoseAccessibilityFocus,
null,
);
}
@@ -220,7 +229,7 @@ class AccessibilityFocusManager {
// a dialog, and nothing else in the dialog is focused. The Flutter
// framework expects that the screen reader will focus on the first (in
// traversal order) focusable element inside the dialog and send a
- // SemanticsAction.focus action. Screen readers on the web do not do
+ // didGainAccessibilityFocus action. Screen readers on the web do not do
// that, and so the web engine has to implement this behavior directly. So
// the dialog will look for a focusable element and request focus on it,
// but now there may be a race between this method unsetting the focus and
diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart
index 0918ef49c3ff7..c48851d9836a2 100644
--- a/lib/web_ui/lib/src/engine/semantics/semantics.dart
+++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart
@@ -2218,6 +2218,8 @@ class EngineSemantics {
'mousemove',
'mouseleave',
'mouseup',
+ 'keyup',
+ 'keydown',
];
if (pointerEventTypes.contains(event.type)) {
diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart
index 3618306d37829..bb79ea1df52d9 100644
--- a/lib/web_ui/lib/src/engine/semantics/text_field.dart
+++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart
@@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'package:ui/ui.dart' as ui;
+import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
+import '../browser_detection.dart' show isIosSafari;
import '../dom.dart';
import '../platform_dispatcher.dart';
import '../text_editing/text_editing.dart';
@@ -120,10 +123,7 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy {
// Android).
// Otherwise, the keyboard stays on screen even when the user navigates to
// a different screen (e.g. by hitting the "back" button).
- // Keep this consistent with how DefaultTextEditingStrategy does it. As of
- // right now, the only difference is that semantic text fields do not
- // participate in form autofill.
- DefaultTextEditingStrategy.scheduleFocusFlutterView(activeDomElement, activeDomElementView);
+ domElement?.blur();
domElement = null;
activeTextField = null;
_queuedStyle = null;
@@ -162,7 +162,7 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy {
if (hasAutofillGroup) {
placeForm();
}
- activeDomElement.focus(preventScroll: true);
+ activeDomElement.focus();
}
@override
@@ -207,40 +207,69 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy {
/// [EngineSemanticsOwner.gestureMode]. However, in Chrome on Android it ignores
/// browser gestures when in pointer mode. In Safari on iOS pointer events are
/// used to detect text box invocation. This is because Safari issues touch
-/// events even when VoiceOver is enabled.
+/// events even when Voiceover is enabled.
class TextField extends PrimaryRoleManager {
TextField(SemanticsObject semanticsObject) : super.blank(PrimaryRole.textField, semanticsObject) {
- _initializeEditableElement();
+ _setupDomElement();
}
- /// The element used for editing, e.g. ``, `