blob: 77c2923fd257622e92504b8abb928ceaddde36fc [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html' as html;
import 'package:ui/ui.dart' as ui;
import '../platform_dispatcher.dart';
import 'semantics.dart';
/// Listens to HTML "click" gestures detected by the browser.
///
/// This gestures is different from the click and tap gestures detected by the
/// framework from raw pointer events. When an assistive technology is enabled
/// the browser may not send us pointer events. In that mode we forward HTML
/// click as [ui.SemanticsAction.tap].
class Tappable extends RoleManager {
Tappable(SemanticsObject semanticsObject)
: super(Role.tappable, semanticsObject);
html.EventListener? _clickListener;
@override
void update() {
final html.Element element = semanticsObject.element;
// "tab-index=0" is used to allow keyboard traversal of non-form elements.
// See also: https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets
element.tabIndex = 0;
semanticsObject.setAriaRole(
'button', semanticsObject.hasFlag(ui.SemanticsFlag.isButton));
// Add `aria-disabled` for disabled buttons.
if (semanticsObject.enabledState() == EnabledState.disabled &&
semanticsObject.hasFlag(ui.SemanticsFlag.isButton)) {
semanticsObject.element.setAttribute('aria-disabled', 'true');
_stopListening();
} else {
semanticsObject.element.removeAttribute('aria-disabled');
// Excluding text fields because text fields have browser-specific logic
// for recognizing taps and activating the keyboard.
if (semanticsObject.hasAction(ui.SemanticsAction.tap) &&
!semanticsObject.hasFlag(ui.SemanticsFlag.isTextField)) {
if (_clickListener == null) {
_clickListener = (_) {
if (semanticsObject.owner.gestureMode !=
GestureMode.browserGestures) {
return;
}
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
semanticsObject.id, ui.SemanticsAction.tap, null);
};
element.addEventListener('click', _clickListener);
}
} else {
_stopListening();
}
}
// Request focus so that the AT shifts a11y focus to this node.
if (semanticsObject.isFlagsDirty && semanticsObject.hasFocus) {
element.focus();
}
}
void _stopListening() {
if (_clickListener == null) {
return;
}
semanticsObject.element.removeEventListener('click', _clickListener);
_clickListener = null;
}
@override
void dispose() {
_stopListening();
semanticsObject.setAriaRole('button', false);
}
}