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

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: remove depth in test 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 2017 The Chromium 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 /**
5 * @template T
6 * @implements {UI.ListDelegate<T>}
7 */
8 UI.SoftDropDown = class {
9 /**
10 * @param {!UI.ListModel<T>} model
11 * @param {!UI.SoftDropDown.Delegate<T>} delegate
12 */
13 constructor(model, delegate) {
14 this._delegate = delegate;
15 this._selectedItem = null;
16 this._model = model;
17
18 this.element = createElementWithClass('button', 'soft-dropdown');
19 var shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'ui/softDro pDownButton.css');
20 this._titleElement = shadowRoot.createChild('span', 'title');
21 var dropdownArrowIcon = UI.Icon.create('smallicon-triangle-down');
22 shadowRoot.appendChild(dropdownArrowIcon);
23
24 this._glassPane = new UI.GlassPane();
25 this._glassPane.setMarginBehavior(UI.GlassPane.MarginBehavior.NoMargin);
26 this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom);
27 this._glassPane.setOutsideClickCallback(this._hide.bind(this));
28 this._glassPane.setPointerEventsBehavior(UI.GlassPane.PointerEventsBehavior. BlockedByGlassPane);
29 this._list = new UI.ListControl(model, this, UI.ListMode.EqualHeightItems);
30 this._list.element.classList.add('item-list');
31 this._rowHeight = 0;
32 this._width = 315;
33 UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'ui/softDr opDown.css')
34 .appendChild(this._list.element);
35
36 this._listWasShowing200msAgo = false;
37 this.element.addEventListener('mousedown', event => {
38 if (this._listWasShowing200msAgo)
39 this._hide(event);
40 else
41 this._show(event);
42 }, false);
43 this.element.addEventListener('keydown', this._onKeyDown.bind(this), false);
44 this.element.addEventListener('focusout', this._hide.bind(this), false);
45 this._list.element.addEventListener('mousedown', event => event.consume(true ), false);
46 this._list.element.addEventListener('mouseup', event => {
47 if (event.target === this._list.element)
48 return;
49
50 if (!this._listWasShowing200msAgo)
51 return;
52 this._selectHighlightedItem();
53 this._hide(event);
54 }, false);
55 model.addEventListener(UI.ListModel.Events.ItemsReplaced, this._itemsReplace d, this);
56 }
57
58 /**
59 * @param {!Event} event
60 */
61 _show(event) {
62 if (this._glassPane.isShowing())
63 return;
64 this._glassPane.setContentAnchorBox(this.element.boxInWindow());
65 this._glassPane.show(/** @type {!Document} **/ (this.element.ownerDocument)) ;
66 this._updateGlasspaneSize();
67 if (this._selectedItem)
68 this._list.selectItem(this._selectedItem);
69 this.element.focus();
70 event.consume(true);
71 setTimeout(() => this._listWasShowing200msAgo = true, 200);
72 }
73
74 _updateGlasspaneSize() {
75 var maxHeight = this._rowHeight * (Math.min(this._model.length(), 9));
76 this._glassPane.setMaxContentSize(new UI.Size(this._width, maxHeight));
77 this._list.viewportResized();
78 }
79
80 /**
81 * @param {!Event} event
82 */
83 _hide(event) {
84 setTimeout(() => this._listWasShowing200msAgo = false, 200);
85 this._glassPane.hide();
86 this._delegate.itemHighlighted(null);
87 event.consume(true);
88 }
89
90 /**
91 * @param {!Event} event
92 */
93 _onKeyDown(event) {
94 var handled = false;
95 switch (event.key) {
96 case 'ArrowLeft':
97 case 'ArrowUp':
caseq 2017/06/06 18:06:00 Can you drop this and UI.ListControl do its job he
einbinder 2017/06/08 23:43:27 No. List control doesn't return whether or not the
98 handled = this._list.selectPreviousItem(false, false);
99 break;
100 case 'ArrowRight':
101 case 'ArrowDown':
102 handled = this._list.selectNextItem(false, false);
103 break;
104 case 'PageUp':
105 handled = this._list.selectItemPreviousPage(false);
106 break;
107 case 'PageDown':
108 handled = this._list.selectItemNextPage(false);
caseq 2017/06/06 18:06:00 ditto.
109 break;
110 case 'Home':
111 for (var i = 0; i < this._model.length(); i++) {
112 if (this.isItemSelectable(this._model.itemAtIndex(i))) {
113 this._list.selectItem(this._model.itemAtIndex(i));
114 handled = true;
115 break;
116 }
117 }
118 break;
119 case 'End':
120 for (var i = this._model.length() - 1; i >= 0; i--) {
121 if (this.isItemSelectable(this._model.itemAtIndex(i))) {
122 this._list.selectItem(this._model.itemAtIndex(i));
123 handled = true;
124 break;
125 }
126 }
127 break;
128 case 'Escape':
129 this._hide(event);
130 break;
131 case 'Tab':
caseq 2017/06/06 18:06:00 Should we also consume the event in all these case
einbinder 2017/06/08 23:43:27 This gets consumed by the hide.
132 if (!this._glassPane.isShowing())
133 break;
134 this._selectHighlightedItem();
135 this._hide(event);
136 break;
137 case 'Enter':
138 if (!this._glassPane.isShowing()) {
139 this._show(event);
140 break;
141 }
142 this._selectHighlightedItem();
143 this._hide(event);
144 break;
145 case ' ':
146 this._show(event);
147 break;
148 default:
149 if (event.key.length === 1) {
150 var selectedIndex = this._list.selectedIndex();
151 var letter = event.key.toUpperCase();
152 for (var i = 0; i < this._model.length(); i++) {
153 var item = this._model.itemAtIndex((selectedIndex + i + 1) % this._m odel.length());
154 if (this._delegate.titleFor(item).toUpperCase().startsWith(letter)) {
155 this._list.selectItem(item);
156 break;
157 }
158 }
159 handled = true;
160 }
161 break;
162 }
163
164 if (handled) {
165 event.consume(true);
166 this._selectHighlightedItem();
167 }
168 }
169
170 /**
171 * @param {number} width
172 */
173 setWidth(width) {
174 this._width = width;
caseq 2017/06/06 18:06:00 _updateGlasspaneSize() perhaps?
einbinder 2017/06/08 23:43:27 Done.
175 }
176
177 /**
178 * @param {number} rowHeight
179 */
180 setRowHeight(rowHeight) {
dgozman 2017/06/06 19:09:21 Why not go to delegate? Looks like artificial rest
einbinder 2017/06/08 23:43:27 This gets really messy with getting the height bac
181 this._rowHeight = rowHeight;
182 }
183
184 /**
185 * @param {!Common.Event} event
186 */
187 _itemsReplaced(event) {
188 var removed = /** @type {!Array<T>} */ (event.data.removed);
189 if (removed.indexOf(this._selectedItem) !== -1) {
190 this._selectedItem = null;
191 this._selectHighlightedItem();
192 }
193 this._updateGlasspaneSize();
194 }
195
196 /**
197 * @param {?T} item
198 */
199 selectItem(item) {
200 this._selectedItem = item;
201 this._updateSelectedItem();
202 }
203
204 /**
205 * @override
206 * @param {T} item
207 * @return {!Element}
208 */
209 createElementForItem(item) {
210 var element = createElementWithClass('div', 'item');
211 element.addEventListener('mousemove', e => {
212 if ((e.movementX || e.movementY) && this._delegate.isItemSelectable(item))
213 this._list.selectItem(item, false, /* Don't scroll */ true);
214 });
215 element.classList.toggle('disabled', !this._delegate.isItemSelectable(item)) ;
216 element.classList.toggle('highlighted', this._list.selectedItem() === item);
217
218 this._delegate.renderElementForItem(element, item);
dgozman 2017/06/06 19:09:21 You should first ask delegate for an item, and the
einbinder 2017/06/08 23:43:27 Done.
219
220 return element;
221 }
222
223 /**
224 * @override
225 * @param {T} item
226 * @return {number}
227 */
228 heightForItem(item) {
229 return this._rowHeight;
230 }
231
232 /**
233 * @override
234 * @param {T} item
235 * @return {boolean}
236 */
237 isItemSelectable(item) {
238 return this._delegate.isItemSelectable(item);
239 }
240
241 /**
242 * @override
243 * @param {?T} from
244 * @param {?T} to
245 * @param {?Element} fromElement
246 * @param {?Element} toElement
247 */
248 selectedItemChanged(from, to, fromElement, toElement) {
249 if (fromElement)
250 fromElement.classList.remove('highlighted');
251 if (toElement)
252 toElement.classList.add('highlighted');
253 this._delegate.itemHighlighted(to);
254 }
255
256 _selectHighlightedItem() {
caseq 2017/06/06 18:06:00 this.selectItem(this._list.selectedItem()), then i
einbinder 2017/06/08 23:43:27 Done.
257 this._selectedItem = this._list.selectedItem();
258 this._updateSelectedItem();
259 }
260
261 _updateSelectedItem() {
262 if (this._selectedItem)
263 this._titleElement.textContent = this._delegate.titleFor(this._selectedIte m);
264 else
265 this._titleElement.textContent = '';
266 this._delegate.itemSelected(this._selectedItem);
267 }
268
269 /**
270 * @param {T} item
271 */
272 refreshItem(item) {
273 this._list.refreshItem(item);
274 }
275 };
276
277 /**
278 * @interface
279 * @template T
280 */
281 UI.SoftDropDown.Delegate = class {
282 /**
283 * @param {T} item
284 * @return {string}
285 */
286 titleFor(item) {
287 }
288
289 /**
290 * @param {!Element} element
291 * @param {T} item
292 */
293 renderElementForItem(element, item) {
caseq 2017/06/06 18:06:00 Why not have it consistent with UI.ListDelegate?
einbinder 2017/06/08 23:43:27 Done.
294 }
295
296 /**
297 * @param {T} item
298 * @return {boolean}
299 */
300 isItemSelectable(item) {
301 }
302
303 /**
304 * @param {?T} item
305 */
306 itemSelected(item) {
dgozman 2017/06/06 19:09:22 Is this like 'onchange' event?
einbinder 2017/06/08 23:43:27 Yes.
307 }
308
309 /**
310 * @param {?T} item
311 */
312 itemHighlighted(item) {
dgozman 2017/06/06 19:09:21 Should we be consistent with list? highlightedItem
einbinder 2017/06/08 23:43:27 The list does that to encourage you setting your o
313 }
314 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698