1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// TODO(yjbanov): TalkBack on Android incorrectly reads state changes for radio 6// buttons. When checking a radio button it reads 7// "Checked, not checked". This is likely due to another radio 8// button automatically becoming unchecked. VoiceOver reads it 9// correctly. It is possible we can fix this by using 10// "radiogroup" and "aria-owns". This may require a change in the 11// framework. Currently the framework does not report the 12// grouping of radio buttons. 13 14part of engine; 15 16/// The specific type of checkable control. 17enum _CheckableKind { 18 /// A checkbox. An element, which has [ui.SemanticsFlag.hasCheckedState] set 19 /// and does not have [ui.SemanticsFlag.isInMutuallyExclusiveGroup] or 20 /// [ui.SemanticsFlag.hasToggledState] state, is marked as a checkbox. 21 checkbox, 22 23 /// A radio button, defined by [ui.SemanticsFlag.isInMutuallyExclusiveGroup]. 24 radio, 25 26 /// A switch, defined by [ui.SemanticsFlag.hasToggledState]. 27 toggle, 28} 29 30/// Renders semantics objects that have checkable (on/off) states. 31/// 32/// Three objects which are implemented by this class are checkboxes, radio 33/// buttons and switches. 34/// 35/// See also [ui.SemanticsFlag.hasCheckedState], [ui.SemanticsFlag.isChecked], 36/// [ui.SemanticsFlag.isInMutuallyExclusiveGroup], [ui.SemanticsFlag.isToggled], 37/// [ui.SemanticsFlag.hasToggledState] 38class Checkable extends RoleManager { 39 _CheckableKind _kind; 40 41 Checkable(SemanticsObject semanticsObject) 42 : super(Role.checkable, semanticsObject) { 43 if (semanticsObject.hasFlag(ui.SemanticsFlag.isInMutuallyExclusiveGroup)) { 44 _kind = _CheckableKind.radio; 45 } else if (semanticsObject.hasFlag(ui.SemanticsFlag.hasToggledState)) { 46 _kind = _CheckableKind.toggle; 47 } else { 48 _kind = _CheckableKind.checkbox; 49 } 50 } 51 52 @override 53 void update() { 54 if (semanticsObject.isFlagsDirty) { 55 switch (_kind) { 56 case _CheckableKind.checkbox: 57 semanticsObject.setAriaRole('checkbox', true); 58 break; 59 case _CheckableKind.radio: 60 semanticsObject.setAriaRole('radio', true); 61 break; 62 case _CheckableKind.toggle: 63 semanticsObject.setAriaRole('switch', true); 64 break; 65 } 66 67 /// Adding disabled and aria-disabled attribute to notify the assistive 68 /// technologies of disabled elements. 69 _updateDisabledAttribute(); 70 71 semanticsObject.element.setAttribute( 72 'aria-checked', 73 (semanticsObject.hasFlag(ui.SemanticsFlag.isChecked) || 74 semanticsObject.hasFlag(ui.SemanticsFlag.isToggled)) 75 ? 'true' 76 : 'false', 77 ); 78 } 79 } 80 81 @override 82 void dispose() { 83 switch (_kind) { 84 case _CheckableKind.checkbox: 85 semanticsObject.setAriaRole('checkbox', false); 86 break; 87 case _CheckableKind.radio: 88 semanticsObject.setAriaRole('radio', false); 89 break; 90 case _CheckableKind.toggle: 91 semanticsObject.setAriaRole('switch', false); 92 break; 93 } 94 _removeDisabledAttribute(); 95 } 96 97 void _updateDisabledAttribute() { 98 if (!semanticsObject.hasFlag(ui.SemanticsFlag.isEnabled)) { 99 final html.Element element = semanticsObject.element; 100 element 101 ..setAttribute('aria-disabled', 'true') 102 ..setAttribute('disabled', 'true'); 103 } else { 104 _removeDisabledAttribute(); 105 } 106 } 107 108 void _removeDisabledAttribute() { 109 final html.Element element = semanticsObject.element; 110 element..removeAttribute('aria-disabled')..removeAttribute('disabled'); 111 } 112} 113