Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(39)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/ui/SoftDropDown.js

Issue 2911363002: DevTools: Split SoftDropdown out of ConsoleContextSelector (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
caseq 2017/05/30 23:40:08 2017 :-)
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 /**
5 * @template T
6 * @implements {UI.ListDelegate<T>}
7 */
8 UI.SoftDropDown = class {
9 /**
10 * @param {!UI.SoftDropDown.Delegate} delegate
11 */
12 constructor(delegate) {
13 this._delegate = delegate;
14 this._selectedItem = null;
15
16 this.element = createElementWithClass('button', 'soft-dropdown');
17 var shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'ui/softDro pDownButton.css');
18 this._titleElement = shadowRoot.createChild('span', 'title');
19 this.element.tabIndex = 0;
20
21 this._glassPane = new UI.GlassPane();
22 this._glassPane.setMarginBehavior(UI.GlassPane.MarginBehavior.NoMargin);
23 this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom);
24 this._glassPane.setOutsideClickCallback(this._hide.bind(this));
25 this._glassPane.setPointerEventsBehavior(UI.GlassPane.PointerEventsBehavior. BlockedByGlassPane);
26 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems);
27 this._list.element.classList.add('item-list');
28 this._list.element.tabIndex = -1;
caseq 2017/05/30 23:40:07 This belongs to UI.ListControl, I'm already moving
einbinder 2017/05/31 21:14:27 I guess that is ok. It removes the ability to have
29 this._rowHeight = 36;
caseq 2017/05/30 23:40:08 Can I have a different rowHeight in my control?
einbinder 2017/05/31 21:14:27 Yeah, but we will need to figure out who owns what
30 UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'ui/softDr opDown.css')
31 .appendChild(this._list.element);
32
33 this._listWasShowing200msAgo = false;
34 this.element.addEventListener('mousedown', event => {
35 if (this._listWasShowing200msAgo)
36 this._hide(event);
37 else
38 this._show(event);
39 }, false);
40 this.element.addEventListener('keydown', this._onKeyDown.bind(this), false);
41 // this.element.addEventListener('focusout', this._hide.bind(this), false);
42 this._list.element.addEventListener('mousedown', event => {
43 event.consume(true);
caseq 2017/05/30 23:40:08 nit: this could be written shorter as event => voi
einbinder 2017/05/31 21:14:27 Done.
44 }, false);
45 this._list.element.addEventListener('mouseup', event => {
46 if (event.target === this._list.element)
47 return;
48
49 if (!this._listWasShowing200msAgo)
50 return;
51 this._selectHighlightedItem();
52 this._hide(event);
53 }, false);
54
55 var dropdownArrowIcon = UI.Icon.create('smallicon-triangle-down');
56 shadowRoot.appendChild(dropdownArrowIcon);
caseq 2017/05/30 23:40:08 please move this up to where we setup this.element
einbinder 2017/05/31 21:14:26 Done.
57 }
58
59 /**
60 * @param {!Event} event
61 */
62 _show(event) {
63 if (this._glassPane.isShowing())
64 return;
65 this._glassPane.setContentAnchorBox(this.element.boxInWindow());
66 this._glassPane.show(/** @type {!Document} **/ (this.element.ownerDocument)) ;
67 this._updateGlasspaneSize();
68 if (this._selectedItem) {
69 this._list.selectItem(this._selectedItem);
70 this._list.scrollItemIntoView(this._selectedItem, true);
caseq 2017/05/30 23:40:07 This should not be necessary, selectItem() above s
einbinder 2017/05/31 21:14:27 Done.
71 }
72 this.element.focus();
caseq 2017/05/30 23:40:08 Do we need focus on the button after _show()?
einbinder 2017/05/31 21:14:27 Yes, because we cancel the mousedown.
73 event.consume(true);
74 setTimeout(() => this._listWasShowing200msAgo = true, 200);
75 }
76
77 _updateGlasspaneSize() {
78 var maxHeight = this._rowHeight * (Math.min(this._list.length(), 9));
79 this._glassPane.setMaxContentSize(new UI.Size(315, maxHeight));
caseq 2017/05/30 23:40:08 Why 315? Also, some use cases may call for larger
einbinder 2017/05/31 21:14:26 Yep.
80 this._list.viewportResized();
81 }
82
83 /**
84 * @param {!Event} event
85 */
86 _hide(event) {
87 setTimeout(() => this._listWasShowing200msAgo = false, 200);
88 this._glassPane.hide();
89 this._delegate.itemHighlighted(null);
90 event.consume(true);
91 }
92
93 /**
94 * @param {!Event} event
95 */
96 _onKeyDown(event) {
97 var handled = false;
98 switch (event.key) {
99 case 'ArrowUp':
caseq 2017/05/30 23:40:07 This and the one below are actually already handle
einbinder 2017/05/31 21:14:27 The soft drop down needs to know if the key was ha
100 handled = this._list.selectPreviousItem(false, false);
101 break;
102 case 'ArrowDown':
103 handled = this._list.selectNextItem(false, false);
104 break;
105 case 'ArrowRight':
caseq 2017/05/30 23:40:08 The very notion of depth is rather specific to the
einbinder 2017/05/31 21:14:26 Putting it back into the ConsoleContextSelector wo
106 var currentItem = this._list.selectedItem();
107 if (!currentItem)
108 break;
109 var nextItem = this._list.itemAtIndex(this._list.selectedIndex() + 1);
110 if (nextItem && this._delegate.depthFor(currentItem) < this._delegate.de pthFor(nextItem))
111 handled = this._list.selectNextItem(false, false);
112 break;
113 case 'ArrowLeft':
114 var currentItem = this._list.selectedItem();
115 if (!currentItem)
116 break;
117 var depth = this._delegate.depthFor(currentItem);
118 for (var i = this._list.selectedIndex() - 1; i >= 0; i--) {
119 if (this._delegate.depthFor(this._list.itemAtIndex(i)) < depth) {
120 handled = true;
121 this._list.selectItem(this._list.itemAtIndex(i), false);
122 break;
123 }
124 }
125 break;
126 case 'PageUp':
caseq 2017/05/30 23:40:08 This and the one below are also handled in UI.List
127 handled = this._list.selectItemPreviousPage(false);
128 break;
129 case 'PageDown':
130 handled = this._list.selectItemNextPage(false);
131 break;
132 case 'Home':
caseq 2017/05/30 23:40:08 Should we add Home/End to UI.ListControl as well?
einbinder 2017/05/31 21:14:26 We can, but that seems like something for a differ
133 for (var i = 0; i < this._list.length(); i++) {
134 if (this.isItemSelectable(this._list.itemAtIndex(i))) {
135 this._list.selectItem(this._list.itemAtIndex(i));
136 handled = true;
137 break;
138 }
139 }
140 break;
141 case 'End':
142 for (var i = this._list.length() - 1; i >= 0; i--) {
143 if (this.isItemSelectable(this._list.itemAtIndex(i))) {
144 this._list.selectItem(this._list.itemAtIndex(i));
145 handled = true;
146 break;
147 }
148 }
149 break;
150 case 'Escape':
151 this._hide(event);
152 break;
153 case 'Tab':
154 if (!this._glassPane.isShowing())
155 break;
156 this._selectHighlightedItem();
157 this._hide(event);
158 break;
159 case 'Enter':
160 if (!this._glassPane.isShowing()) {
161 this._show(event);
162 break;
163 }
164 this._selectHighlightedItem();
165 this._hide(event);
166 break;
167 case ' ':
168 this._show(event);
169 break;
170 default:
171 if (event.key.length === 1) {
172 var selectedIndex = this._list.selectedIndex();
173 var letter = event.key.toUpperCase();
174 for (var i = 0; i < this._list.length(); i++) {
175 var item = this._list.itemAtIndex((selectedIndex + i + 1) % this._li st.length());
176 if (this._delegate.titleFor(item).toUpperCase().startsWith(letter)) {
177 this._list.selectItem(item);
178 break;
179 }
180 }
181 handled = true;
182 }
183 break;
184 }
185
186 if (handled) {
187 event.consume(true);
188 this._selectHighlightedItem();
189 }
190 }
191
192 /**
193 * @param {T} item
194 * @param {function(T, T):number} comparator
195 */
196 insertItemWithComparator(item, comparator) {
197 this._list.insertItemWithComparator(item, comparator);
198 }
199
200 /**
201 * @param {T} item
202 */
203 removeItem(item) {
204 if (this._list.indexOfItem(item) !== -1)
caseq 2017/05/30 23:40:08 Do we really need this check?
einbinder 2017/05/31 21:14:26 The list asserts that the item exists before it ca
205 this._list.removeItem(item);
206 if (this._selectedItem === item) {
207 this._selectedItem = null;
208 this._selectHighlightedItem();
209 }
210 this._updateGlasspaneSize();
211 }
212
213 /**
214 * @param {?T} item
215 */
216 selectItem(item) {
217 this._selectedItem = item;
caseq 2017/05/30 23:40:08 Do we need our own copy of the selected item? Perh
einbinder 2017/05/31 21:14:27 The selection in the dropdown is separate from tha
218 this._updateSelectedItem();
219 }
220
221 /**
222 * @override
223 * @param {T} item
224 * @return {!Element}
225 */
226 createElementForItem(item) {
227 var element = this._delegate.elementForItem(item);
228 element.style.paddingLeft = (8 + this._delegate.depthFor(item) * 15) + 'px';
caseq 2017/05/30 23:40:08 This is too specific, let's keep in in the console
einbinder 2017/05/31 21:14:27 If we go with the SoftDropDown handling depth, it
einbinder 2017/06/05 21:03:39 Removed depth-aware keyboard shortcuts, and moved
229 element.addEventListener('mousemove', e => {
caseq 2017/05/30 23:40:08 Let's have one mousemove listener for the entire c
einbinder 2017/05/31 21:14:27 Why?
230 if ((e.movementX || e.movementY) && this._delegate.isItemSelectable(item))
231 this._list.selectItem(item, false, /* Don't scroll */ true);
232 });
233 element.classList.toggle('item', true);
234 element.classList.toggle('disabled', !this._delegate.isItemSelectable(item)) ;
235 element.classList.toggle('selected', this._list.selectedItem() === item);
236 return element;
237 }
238
239 /**
240 * @override
241 * @param {T} item
242 * @return {number}
243 */
244 heightForItem(item) {
245 return this._rowHeight;
246 }
247
248 /**
249 * @override
250 * @param {T} item
251 * @return {boolean}
252 */
253 isItemSelectable(item) {
254 return this._delegate.isItemSelectable(item);
255 }
256
257 /**
258 * @override
259 * @param {?T} from
260 * @param {?T} to
261 * @param {?Element} fromElement
262 * @param {?Element} toElement
263 */
264 selectedItemChanged(from, to, fromElement, toElement) {
265 if (fromElement)
266 fromElement.classList.remove('selected');
267 if (toElement)
268 toElement.classList.add('selected');
269 this._delegate.itemHighlighted(to);
270 }
271
272 _selectHighlightedItem() {
273 this._selectedItem = this._list.selectedItem();
274 this._updateSelectedItem();
275 }
276
277 _updateSelectedItem() {
278 if (this._selectedItem)
279 this._titleElement.textContent = this._delegate.titleFor(this._selectedIte m);
280 else
281 this._titleElement.textContent = '';
282 this._delegate.itemSelected(this._selectedItem);
283 }
284
285 /**
286 * @param {T} item
287 */
288 refreshItem(item) {
289 var index = this._list.indexOfItem(item);
290 this._list.refreshItemsInRange(index, index + 1);
291 }
292 };
293
294 /**
295 * @interface
296 * @template T
297 */
298 UI.SoftDropDown.Delegate = class {
299 /**
300 * @param {T} item
301 * @return {string}
302 */
303 titleFor(item) {
304 }
305
306 /**
307 * @param {T} item
308 * @return {number}
309 */
310 depthFor(item) {
311 }
312
313 /**
314 * @param {T} item
315 * @return {!Element}
316 */
317 elementForItem(item) {
318 }
319
320 /**
321 * @param {T} item
322 * @return {boolean}
323 */
324 isItemSelectable(item) {
325 }
326
327 /**
328 * @param {?T} item
329 */
330 itemSelected(item) {
331 }
332
333 /**
334 * @param {?T} item
335 */
336 itemHighlighted(item) {
337 }
338 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698