OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 cr.define('cr.ui', function() { | 5 cr.define('cr.ui', function() { |
6 /** | 6 /** |
7 * A class to manage focus between given horizontally arranged elements. | 7 * A class to manage focus between given horizontally arranged elements. |
8 * For example, given the page: | 8 * For example, given the page: |
9 * | 9 * |
10 * <input type="checkbox"> <label>Check me!</label> <button>X</button> | 10 * <input type="checkbox"> <label>Check me!</label> <button>X</button> |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
47 onKeydown: assertNotReached, | 47 onKeydown: assertNotReached, |
48 | 48 |
49 /** | 49 /** |
50 * @param {cr.ui.FocusRow} row The row that detected the mouse going down. | 50 * @param {cr.ui.FocusRow} row The row that detected the mouse going down. |
51 * @param {Event} e | 51 * @param {Event} e |
52 * @return {boolean} Whether the event was handled. | 52 * @return {boolean} Whether the event was handled. |
53 */ | 53 */ |
54 onMousedown: assertNotReached, | 54 onMousedown: assertNotReached, |
55 }; | 55 }; |
56 | 56 |
| 57 /** @const {string} */ |
| 58 FocusRow.ACTIVE_CLASS = 'focus-row-active'; |
| 59 |
57 FocusRow.prototype = { | 60 FocusRow.prototype = { |
58 __proto__: HTMLDivElement.prototype, | 61 __proto__: HTMLDivElement.prototype, |
59 | 62 |
60 /** | 63 /** |
61 * Should be called in the constructor to decorate |this|. | 64 * Should be called in the constructor to decorate |this|. |
62 * @param {Node} boundary Focus events are ignored outside of this node. | 65 * @param {Node} boundary Focus events are ignored outside of this node. |
63 * @param {cr.ui.FocusRow.Delegate=} opt_delegate A delegate to handle key | 66 * @param {cr.ui.FocusRow.Delegate=} opt_delegate A delegate to handle key |
64 * events. | 67 * events. |
65 */ | 68 */ |
66 decorate: function(boundary, opt_delegate) { | 69 decorate: function(boundary, opt_delegate) { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
98 * Add an element to this FocusRow. No-op if |element| is not provided. | 101 * Add an element to this FocusRow. No-op if |element| is not provided. |
99 * @param {Element} element The element that should be added. | 102 * @param {Element} element The element that should be added. |
100 */ | 103 */ |
101 addFocusableElement: function(element) { | 104 addFocusableElement: function(element) { |
102 if (!element) | 105 if (!element) |
103 return; | 106 return; |
104 | 107 |
105 assert(this.focusableElements.indexOf(element) == -1); | 108 assert(this.focusableElements.indexOf(element) == -1); |
106 assert(this.contains(element)); | 109 assert(this.contains(element)); |
107 | 110 |
| 111 element.tabIndex = this.isActive() ? 0 : -1; |
| 112 |
108 this.focusableElements.push(element); | 113 this.focusableElements.push(element); |
109 this.eventTracker_.add(element, 'mousedown', | 114 this.eventTracker_.add(element, 'mousedown', |
110 this.onMousedown_.bind(this)); | 115 this.onMousedown_.bind(this)); |
111 }, | 116 }, |
112 | 117 |
113 /** | 118 /** |
114 * Called when focus changes to activate/deactivate the row. Focus is | 119 * Called when focus changes to activate/deactivate the row. Focus is |
115 * removed from the row when |element| is not in the FocusRow. | 120 * removed from the row when |element| is not in the FocusRow. |
116 * @param {Element} element The element that has focus. null if focus should | 121 * @param {Element} element The element that has focus. null if focus should |
117 * be removed. | 122 * be removed. |
118 * @private | 123 * @private |
119 */ | 124 */ |
120 onFocusChange_: function(element) { | 125 onFocusChange_: function(element) { |
121 var isActive = this.contains(element); | 126 this.makeActive(this.contains(element)); |
122 var wasActive = this.classList.contains('focus-row-active'); | 127 }, |
123 | 128 |
124 // Only send events if the active state is different for the row. | 129 /** @return {boolean} Whether this row is currently active. */ |
125 if (isActive != wasActive) | 130 isActive: function() { |
126 this.makeRowActive(isActive); | 131 return this.classList.contains(FocusRow.ACTIVE_CLASS); |
127 }, | 132 }, |
128 | 133 |
129 /** | 134 /** |
130 * Enables/disables the tabIndex of the focusable elements in the FocusRow. | 135 * Enables/disables the tabIndex of the focusable elements in the FocusRow. |
131 * tabIndex can be set properly. | 136 * tabIndex can be set properly. |
132 * @param {boolean} active True if tab is allowed for this row. | 137 * @param {boolean} active True if tab is allowed for this row. |
133 */ | 138 */ |
134 makeRowActive: function(active) { | 139 makeActive: function(active) { |
| 140 if (active == this.isActive()) |
| 141 return; |
| 142 |
135 this.focusableElements.forEach(function(element) { | 143 this.focusableElements.forEach(function(element) { |
136 element.tabIndex = active ? 0 : -1; | 144 element.tabIndex = active ? 0 : -1; |
137 }); | 145 }); |
138 | 146 |
139 this.classList.toggle('focus-row-active', active); | 147 this.classList.toggle(FocusRow.ACTIVE_CLASS, active); |
140 this.onActiveStateChanged(active); | 148 this.onActiveStateChanged(active); |
141 }, | 149 }, |
142 | 150 |
143 /** Call this to clean up event handling before dereferencing. */ | 151 /** Dereferences nodes and removes event handlers. */ |
144 destroy: function() { | 152 destroy: function() { |
| 153 this.focusableElements.length = 0; |
145 this.eventTracker_.removeAll(); | 154 this.eventTracker_.removeAll(); |
146 }, | 155 }, |
147 | 156 |
148 /** | 157 /** |
149 * @param {Event} e The focusin event. | 158 * @param {Event} e The focusin event. |
150 * @private | 159 * @private |
151 */ | 160 */ |
152 onFocusin_: function(e) { | 161 onFocusin_: function(e) { |
153 var target = assertInstanceof(e.target, Element); | 162 var target = assertInstanceof(e.target, Element); |
154 if (this.boundary_.contains(target)) | 163 if (this.boundary_.contains(target)) |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
200 // Focus this row if the target is one of the elements in this row. | 209 // Focus this row if the target is one of the elements in this row. |
201 this.onFocusChange_(assertInstanceof(e.target, Element)); | 210 this.onFocusChange_(assertInstanceof(e.target, Element)); |
202 } | 211 } |
203 }, | 212 }, |
204 }; | 213 }; |
205 | 214 |
206 return { | 215 return { |
207 FocusRow: FocusRow, | 216 FocusRow: FocusRow, |
208 }; | 217 }; |
209 }); | 218 }); |
OLD | NEW |