OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 Polymer({ | 5 Polymer({ |
6 is: 'bookmarks-folder-node', | 6 is: 'bookmarks-folder-node', |
7 | 7 |
8 behaviors: [ | 8 behaviors: [ |
9 bookmarks.StoreClient, | 9 bookmarks.StoreClient, |
10 Polymer.IronA11yKeysBehavior, | |
10 ], | 11 ], |
11 | 12 |
12 properties: { | 13 properties: { |
13 itemId: { | 14 itemId: { |
14 type: String, | 15 type: String, |
15 observer: 'updateFromStore', | 16 observer: 'updateFromStore', |
16 }, | 17 }, |
17 | 18 |
18 depth: { | 19 depth: { |
19 type: Number, | 20 type: Number, |
20 observer: 'depthChanged_', | 21 observer: 'depthChanged_', |
21 }, | 22 }, |
22 | 23 |
24 focusable: { | |
25 type: Boolean, | |
26 value: false, | |
27 }, | |
28 | |
23 /** @type {BookmarkNode} */ | 29 /** @type {BookmarkNode} */ |
24 item_: Object, | 30 item_: Object, |
25 | 31 |
26 /** @private */ | 32 /** @private */ |
27 isClosed_: Boolean, | 33 isClosed_: Boolean, |
28 | 34 |
29 /** @private */ | 35 /** @private */ |
30 selectedFolder_: String, | 36 selectedFolder_: String, |
31 | 37 |
32 /** @private */ | 38 /** @private */ |
33 searchActive_: Boolean, | 39 searchActive_: Boolean, |
34 | 40 |
35 /** @private */ | 41 /** @private */ |
36 isSelectedFolder_: { | 42 isSelectedFolder_: { |
37 type: Boolean, | 43 type: Boolean, |
38 value: false, | 44 value: false, |
39 reflectToAttribute: true, | 45 reflectToAttribute: true, |
40 computed: 'computeIsSelected_(itemId, selectedFolder_, searchActive_)' | 46 computed: 'computeIsSelected_(itemId, selectedFolder_, searchActive_)' |
41 }, | 47 }, |
42 }, | 48 }, |
43 | 49 |
50 listeners: { | |
51 'keydown': 'onKeydown_', | |
52 }, | |
53 | |
44 /** @override */ | 54 /** @override */ |
45 attached: function() { | 55 attached: function() { |
46 this.watch('item_', function(state) { | 56 this.watch('item_', function(state) { |
47 return state.nodes[this.itemId]; | 57 return state.nodes[this.itemId]; |
48 }.bind(this)); | 58 }.bind(this)); |
49 this.watch('isClosed_', function(state) { | 59 this.watch('isClosed_', function(state) { |
50 return state.closedFolders.has(this.itemId); | 60 return state.closedFolders.has(this.itemId); |
51 }.bind(this)); | 61 }.bind(this)); |
52 this.watch('selectedFolder_', function(state) { | 62 this.watch('selectedFolder_', function(state) { |
53 return state.selectedFolder; | 63 return state.selectedFolder; |
54 }); | 64 }); |
55 this.watch('searchActive_', function(state) { | 65 this.watch('searchActive_', function(state) { |
56 return bookmarks.util.isShowingSearch(state); | 66 return bookmarks.util.isShowingSearch(state); |
57 }); | 67 }); |
58 | 68 |
59 this.updateFromStore(); | 69 this.updateFromStore(); |
70 | |
71 if (this.selectedFolder_ == this.itemId) | |
72 this.fire('folder-node-focus-changed', this); | |
60 }, | 73 }, |
61 | 74 |
62 /** @return {HTMLElement} */ | 75 /** @return {HTMLElement} */ |
76 getFocusTarget: function() { | |
77 return this.$.container; | |
78 }, | |
79 | |
80 /** @return {HTMLElement} */ | |
63 getDropTarget: function() { | 81 getDropTarget: function() { |
64 return this.$.container; | 82 return this.$.container; |
65 }, | 83 }, |
66 | 84 |
67 /** @return {boolean} */ | 85 /** @return {boolean} */ |
68 isPositiveDepth_: function() { | 86 isPositiveDepth_: function(depth) { |
69 return this.depth >= 0; | 87 return depth >= 0; |
70 }, | 88 }, |
71 | 89 |
72 /** | 90 /** |
91 * @private | |
92 * @param {!Event} e | |
93 */ | |
94 onKeydown_: function(e) { | |
95 var direction = 0; | |
96 var handled = true; | |
97 if (this.keyboardEventMatchesKeys(e, 'up')) { | |
tsergeant
2017/04/19 01:42:16
Seems like overkill to use IronA11yKeysBehavior ju
calamity
2017/04/19 04:51:02
Done.
| |
98 direction = -1; | |
99 } else if (this.keyboardEventMatchesKeys(e, 'down')) { | |
100 direction = 1; | |
101 } else { | |
102 handled = false; | |
103 } | |
104 | |
tsergeant
2017/04/19 01:42:16
Is left/right going to be handled here too? Maybe
calamity
2017/04/19 04:51:02
Yeah. It's in a follow-up.
| |
105 if (direction) { | |
106 handled = | |
107 this.changeKeyboardSelection_(direction, this.root.activeElement); | |
108 } | |
109 | |
110 if (!handled) | |
111 return; | |
112 | |
113 e.preventDefault(); | |
114 e.stopPropagation(); | |
115 }, | |
116 | |
117 /** | |
118 * @private | |
119 * @param {number} direction | |
120 * @return {boolean} Whether the event should be treated as handled. | |
121 */ | |
122 changeKeyboardSelection_: function(direction, currentFocus) { | |
123 var isCurrentlyFocused = currentFocus == this.$.container; | |
124 | |
125 // Get the next folder node. If there is no next element, the event will | |
126 // bubble to the parent node which will handle it. | |
127 var newFocusFolderNode = this.getNextVisibleFolderNode_( | |
128 direction == -1, isCurrentlyFocused ? this : currentFocus); | |
129 | |
130 if (!newFocusFolderNode || newFocusFolderNode.itemId != ROOT_NODE_ID) { | |
tsergeant
2017/04/19 01:42:16
This can be simplified a little bit:
if (!newFocu
calamity
2017/04/19 04:51:02
Done.
| |
131 if (newFocusFolderNode) { | |
132 this.fire('folder-node-focus-changed', newFocusFolderNode); | |
133 newFocusFolderNode.getFocusTarget().focus(); | |
134 } else { | |
135 // If there is no newly focused node, allow the event to bubble. | |
136 return false; | |
137 } | |
138 } | |
139 return true; | |
140 }, | |
141 | |
142 /** | |
143 * Returns the next or previous visible bookmark node relative to |current|. | |
144 * @private | |
145 * @param {boolean} reverse | |
146 * @param {BookmarksFolderNodeElement} current Either this node, or an | |
147 * immediate child. | |
148 * @return {HTMLElement|null} Returns null | |
tsergeant
2017/04/19 01:42:16
Returns null...when?
calamity
2017/04/19 04:51:02
Done.
| |
149 */ | |
150 getNextVisibleFolderNode_: function(reverse, current) { | |
151 var newFocus = null; | |
152 var children = | |
153 Array.from(this.root.querySelectorAll('bookmarks-folder-node')); | |
154 | |
155 // The current node's successor can only be its first child. | |
156 if (this == current) { | |
157 if (!reverse && children.length && !this.isClosed_) | |
158 newFocus = children[0]; | |
159 | |
160 return newFocus; | |
161 } | |
162 | |
163 var index = children.indexOf(current); | |
164 assert(index != -1); | |
165 if (reverse) { | |
166 // A child node's predecessor is either the previous child's last visible | |
167 // descendant, or this node, which is its immediate parent. | |
168 newFocus = | |
169 index == 0 ? this : children[index - 1].getLastVisibleDescendant_(); | |
170 } else if (index < children.length - 1) { | |
171 // A successor to a child is the next child. | |
172 newFocus = children[index + 1]; | |
173 } | |
174 | |
175 return newFocus; | |
176 }, | |
177 | |
178 /** | |
179 * @private | |
180 * @return {BookmarksFolderNodeElement} | |
181 */ | |
182 getLastVisibleDescendant_: function() { | |
183 var children = | |
184 Array.from(this.root.querySelectorAll('bookmarks-folder-node')); | |
185 | |
186 if (this.isClosed_ || children.length == 0) | |
187 return this; | |
188 | |
189 return children.pop().getLastVisibleDescendant_(); | |
190 }, | |
191 | |
192 /** | |
73 * @private | 193 * @private |
74 * @return {string} | 194 * @return {string} |
75 */ | 195 */ |
76 getFolderIcon_: function() { | 196 getFolderIcon_: function() { |
77 return this.isSelectedFolder_ ? 'bookmarks:folder-open' : 'cr:folder'; | 197 return this.isSelectedFolder_ ? 'bookmarks:folder-open' : 'cr:folder'; |
78 }, | 198 }, |
79 | 199 |
80 /** @private */ | 200 /** @private */ |
81 selectFolder_: function() { | 201 selectFolder_: function() { |
82 this.dispatch(bookmarks.actions.selectFolder(this.item_.id)); | 202 this.dispatch(bookmarks.actions.selectFolder(this.item_.id)); |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
133 return !this.getState().nodes[itemId].url; | 253 return !this.getState().nodes[itemId].url; |
134 }, | 254 }, |
135 | 255 |
136 /** | 256 /** |
137 * @private | 257 * @private |
138 * @return {boolean} | 258 * @return {boolean} |
139 */ | 259 */ |
140 isRootFolder_: function() { | 260 isRootFolder_: function() { |
141 return this.depth == 0; | 261 return this.depth == 0; |
142 }, | 262 }, |
263 | |
264 getTabIndex_: function(focusable) { | |
265 return focusable ? 0 : -1; | |
266 }, | |
143 }); | 267 }); |
OLD | NEW |