| // 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. |
| |
| // @dart = 2.6 |
| part of engine; |
| |
| /// Represents semantic objects that deliver information in a visual manner. |
| /// |
| /// Uses aria img role to convey this semantic information to the element. |
| /// |
| /// Screen-readers takes advantage of "aria-label" to describe the visual. |
| class ImageRoleManager extends RoleManager { |
| ImageRoleManager(SemanticsObject semanticsObject) |
| : super(Role.image, semanticsObject); |
| |
| /// The element with role="img" and aria-label could block access to all |
| /// children elements, therefore create an auxiliary element and describe the |
| /// image in that if the semantic object have child nodes. |
| html.Element _auxiliaryImageElement; |
| |
| @override |
| void update() { |
| if (semanticsObject.isVisualOnly && semanticsObject.hasChildren) { |
| if (_auxiliaryImageElement == null) { |
| _auxiliaryImageElement = html.Element.tag('flt-semantics-img'); |
| // Absolute positioning and sizing of leaf text elements confuses |
| // VoiceOver. So we let the browser size the value node. The node will |
| // still have a bigger tap area. However, if the node is a parent to |
| // other nodes, then VoiceOver behaves as expected with absolute |
| // positioning and sizing. |
| if (semanticsObject.hasChildren) { |
| _auxiliaryImageElement.style |
| ..position = 'absolute' |
| ..top = '0' |
| ..left = '0' |
| ..width = '${semanticsObject.rect.width}px' |
| ..height = '${semanticsObject.rect.height}px'; |
| } |
| _auxiliaryImageElement.style.fontSize = '6px'; |
| semanticsObject.element.append(_auxiliaryImageElement); |
| } |
| |
| _auxiliaryImageElement.setAttribute('role', 'img'); |
| _setLabel(_auxiliaryImageElement); |
| } else if (semanticsObject.isVisualOnly) { |
| semanticsObject.setAriaRole('img', true); |
| _setLabel(semanticsObject.element); |
| _cleanUpAuxiliaryElement(); |
| } else { |
| _cleanUpAuxiliaryElement(); |
| _cleanupElement(); |
| } |
| } |
| |
| void _setLabel(html.Element element) { |
| if (semanticsObject.hasLabel) { |
| element.setAttribute('aria-label', semanticsObject.label); |
| } |
| } |
| |
| void _cleanUpAuxiliaryElement() { |
| if (_auxiliaryImageElement != null) { |
| _auxiliaryImageElement.remove(); |
| _auxiliaryImageElement = null; |
| } |
| } |
| |
| void _cleanupElement() { |
| semanticsObject.setAriaRole('img', false); |
| semanticsObject.element.attributes.remove('aria-label'); |
| } |
| |
| @override |
| void dispose() { |
| _cleanUpAuxiliaryElement(); |
| _cleanupElement(); |
| } |
| } |