• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5part of engine;
6
7/// Adds increment/decrement event handling to a semantics object.
8///
9/// The implementation uses a hidden `<input type="range">` element with ARIA
10/// attributes to cause the browser to render increment/decrement controls to
11/// the assistive technology.
12///
13/// The input element is disabled whenever the gesture mode switches to pointer
14/// events. This is to prevent the browser from taking over drag gestures. Drag
15/// gestures must be interpreted by the Flutter framework.
16class Incrementable extends RoleManager {
17  /// The HTML element used to render semantics to the browser.
18  final html.InputElement _element = html.InputElement();
19
20  /// The value used by the input element.
21  ///
22  /// Flutter values are strings, and are not necessarily numbers. In order to
23  /// convey to the browser what the available "range" of values is we
24  /// substitute the framework value with a generated `int` surrogate.
25  /// "aria-valuetext" attribute is used to cause the browser to announce the
26  /// framework value to the user.
27  int _currentSurrogateValue = 1;
28
29  /// Disables the input [_element] when the gesture mode switches to
30  /// [GestureMode.pointerEvents], and enables it when the mode switches back to
31  /// [GestureMode.browserGestures].
32  GestureModeCallback _gestureModeListener;
33
34  /// Whether we forwarded a semantics action to the framework and awaiting an
35  /// update.
36  ///
37  /// This field is used to determine whether the HTML DOM of the semantics
38  /// tree should be updated.
39  bool _pendingResync = false;
40
41  Incrementable(SemanticsObject semanticsObject)
42      : super(Role.incrementable, semanticsObject) {
43    semanticsObject.element.append(_element);
44    _element.type = 'range';
45    _element.setAttribute('role', 'slider');
46
47    _element.addEventListener('change', (_) {
48      if (_element.disabled) {
49        return;
50      }
51      _pendingResync = true;
52      final int newInputValue = int.parse(_element.value);
53      if (newInputValue > _currentSurrogateValue) {
54        _currentSurrogateValue += 1;
55        ui.window.onSemanticsAction(
56            semanticsObject.id, ui.SemanticsAction.increase, null);
57      } else if (newInputValue < _currentSurrogateValue) {
58        _currentSurrogateValue -= 1;
59        ui.window.onSemanticsAction(
60            semanticsObject.id, ui.SemanticsAction.decrease, null);
61      }
62    });
63
64    // Store the callback as a closure because Dart does not guarantee that
65    // tear-offs produce the same function object.
66    _gestureModeListener = (GestureMode mode) {
67      update();
68    };
69    semanticsObject.owner.addGestureModeListener(_gestureModeListener);
70  }
71
72  @override
73  void update() {
74    switch (semanticsObject.owner.gestureMode) {
75      case GestureMode.browserGestures:
76        _enableBrowserGestureHandling();
77        _updateInputValues();
78        break;
79      case GestureMode.pointerEvents:
80        _disableBrowserGestureHandling();
81        break;
82    }
83  }
84
85  void _enableBrowserGestureHandling() {
86    assert(semanticsObject.owner.gestureMode == GestureMode.browserGestures);
87    if (!_element.disabled) {
88      return;
89    }
90    _element.disabled = false;
91  }
92
93  void _updateInputValues() {
94    assert(semanticsObject.owner.gestureMode == GestureMode.browserGestures);
95
96    final bool updateNeeded = _pendingResync ||
97        semanticsObject.isValueDirty ||
98        semanticsObject.isIncreasedValueDirty ||
99        semanticsObject.isDecreasedValueDirty;
100
101    if (!updateNeeded) {
102      return;
103    }
104
105    _pendingResync = false;
106
107    final String surrogateTextValue = '$_currentSurrogateValue';
108    _element.value = surrogateTextValue;
109    _element.setAttribute('aria-valuenow', surrogateTextValue);
110    _element.setAttribute('aria-valuetext', semanticsObject.value);
111
112    final bool canIncrease = semanticsObject.increasedValue != null;
113    final String surrogateMaxTextValue =
114        canIncrease ? '${_currentSurrogateValue + 1}' : surrogateTextValue;
115    _element.max = surrogateMaxTextValue;
116    _element.setAttribute('aria-valuemax', surrogateMaxTextValue);
117
118    final bool canDecrease = semanticsObject.decreasedValue != null;
119    final String surrogateMinTextValue =
120        canDecrease ? '${_currentSurrogateValue - 1}' : surrogateTextValue;
121    _element.min = surrogateMinTextValue;
122    _element.setAttribute('aria-valuemin', surrogateMinTextValue);
123  }
124
125  void _disableBrowserGestureHandling() {
126    if (_element.disabled) {
127      return;
128    }
129    _element.disabled = true;
130  }
131
132  @override
133  void dispose() {
134    assert(_gestureModeListener != null);
135    semanticsObject.owner.removeGestureModeListener(_gestureModeListener);
136    _gestureModeListener = null;
137    _disableBrowserGestureHandling();
138    _element.remove();
139  }
140}
141