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

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

Issue 2592433003: [DevTools] Replace ViewportControl with ListControl. (Closed)
Patch Set: small fix Created 4 years 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
1 /* 1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved. 2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are 5 * modification, are permitted provided that the following conditions are
6 * met: 6 * met:
7 * 7 *
8 * * Redistributions of source code must retain the above copyright 8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above 10 * * Redistributions in binary form must reproduce the above
(...skipping 28 matching lines...) Expand all
39 */ 39 */
40 applySuggestion(suggestion, isIntermediateSuggestion) {}, 40 applySuggestion(suggestion, isIntermediateSuggestion) {},
41 41
42 /** 42 /**
43 * acceptSuggestion will be always called after call to applySuggestion with i sIntermediateSuggestion being equal to false. 43 * acceptSuggestion will be always called after call to applySuggestion with i sIntermediateSuggestion being equal to false.
44 */ 44 */
45 acceptSuggestion() {}, 45 acceptSuggestion() {},
46 }; 46 };
47 47
48 /** 48 /**
49 * @implements {UI.ViewportControl.Provider}
50 * @unrestricted 49 * @unrestricted
50 * @implements {UI.ListDelegate}
51 */ 51 */
52 UI.SuggestBox = class { 52 UI.SuggestBox = class {
53 /** 53 /**
54 * @param {!UI.SuggestBoxDelegate} suggestBoxDelegate 54 * @param {!UI.SuggestBoxDelegate} suggestBoxDelegate
55 * @param {number=} maxItemsHeight 55 * @param {number=} maxItemsHeight
56 * @param {boolean=} captureEnter 56 * @param {boolean=} captureEnter
57 */ 57 */
58 constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) { 58 constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) {
59 this._suggestBoxDelegate = suggestBoxDelegate; 59 this._suggestBoxDelegate = suggestBoxDelegate;
60 this._length = 0;
61 this._selectedIndex = -1;
62 this._selectedElement = null;
63 this._maxItemsHeight = maxItemsHeight; 60 this._maxItemsHeight = maxItemsHeight;
64 this._maybeHideBound = this._maybeHide.bind(this); 61 this._maybeHideBound = this._maybeHide.bind(this);
65 this._container = createElementWithClass('div', 'suggest-box-container'); 62 this._container = createElementWithClass('div', 'suggest-box-container');
66 this._viewport = new UI.ViewportControl(this); 63 this._rowHeight = 17;
67 this._element = this._viewport.element; 64 /** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */
65 this._list = new UI.ListControl(this, false);
66 this._element = this._list.element;
68 this._element.classList.add('suggest-box'); 67 this._element.classList.add('suggest-box');
69 this._container.appendChild(this._element); 68 this._container.appendChild(this._element);
70 this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true); 69 this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true);
71 this._detailsPopup = this._container.createChild('div', 'suggest-box details -popup monospace'); 70 this._detailsPopup = this._container.createChild('div', 'suggest-box details -popup monospace');
72 this._detailsPopup.classList.add('hidden'); 71 this._detailsPopup.classList.add('hidden');
73 this._asyncDetailsCallback = null; 72 this._asyncDetailsCallback = null;
74 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */ 73 /** @type {!Map<!UI.SuggestBox.Suggestion, !Promise<{detail: string, descrip tion: string}>>} */
75 this._asyncDetailsPromises = new Map(); 74 this._asyncDetailsPromises = new Map();
76 this._userInteracted = false; 75 this._userInteracted = false;
77 this._captureEnter = captureEnter; 76 this._captureEnter = captureEnter;
78 /** @type {!Array<!Element>} */
79 this._elementList = [];
80 this._rowHeight = 17;
81 this._viewportWidth = '100vw'; 77 this._viewportWidth = '100vw';
82 this._hasVerticalScroll = false; 78 this._hasVerticalScroll = false;
83 this._userEnteredText = ''; 79 this._userEnteredText = '';
84 /** @type {!UI.SuggestBox.Suggestions} */
85 this._items = [];
86 } 80 }
87 81
88 /** 82 /**
89 * @return {boolean} 83 * @return {boolean}
90 */ 84 */
91 visible() { 85 visible() {
92 return !!this._container.parentElement; 86 return !!this._container.parentElement;
93 } 87 }
94 88
95 /** 89 /**
96 * @param {!AnchorBox} anchorBox 90 * @param {!AnchorBox} anchorBox
97 */ 91 */
98 setPosition(anchorBox) { 92 setPosition(anchorBox) {
99 this._updateBoxPosition(anchorBox); 93 this._updateBoxPosition(anchorBox, this._list.length());
100 } 94 }
101 95
102 /** 96 /**
103 * @param {!AnchorBox} anchorBox 97 * @param {!AnchorBox} anchorBox
98 * @param {number} length
104 */ 99 */
105 _updateBoxPosition(anchorBox) { 100 _updateBoxPosition(anchorBox, length) {
106 console.assert(this._overlay); 101 console.assert(this._overlay);
107 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._la stItemCount === this.itemCount()) 102 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._la stItemCount === length)
108 return; 103 return;
109 this._lastItemCount = this.itemCount(); 104 this._lastItemCount = length;
110 this._lastAnchorBox = anchorBox; 105 this._lastAnchorBox = anchorBox;
111 106
112 // Position relative to main DevTools element. 107 // Position relative to main DevTools element.
113 var container = UI.Dialog.modalHostView().element; 108 var container = UI.Dialog.modalHostView().element;
114 anchorBox = anchorBox.relativeToElement(container); 109 anchorBox = anchorBox.relativeToElement(container);
115 var totalHeight = container.offsetHeight; 110 var totalHeight = container.offsetHeight;
116 var aboveHeight = anchorBox.y; 111 var aboveHeight = anchorBox.y;
117 var underHeight = totalHeight - anchorBox.y - anchorBox.height; 112 var underHeight = totalHeight - anchorBox.y - anchorBox.height;
118 113
119 this._overlay.setLeftOffset(anchorBox.x); 114 this._overlay.setLeftOffset(anchorBox.x);
120 115
121 var under = underHeight >= aboveHeight; 116 var under = underHeight >= aboveHeight;
122 if (under) 117 if (under)
123 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true); 118 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true);
124 else 119 else
125 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false); 120 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false);
126 121
127 var spacer = 6; 122 var spacer = 6;
128 var maxHeight = Math.min( 123 var maxHeight = Math.min(
129 Math.max(underHeight, aboveHeight) - spacer, 124 Math.max(underHeight, aboveHeight) - spacer,
130 this._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : Infinity ); 125 this._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : Infinity );
131 var height = this._rowHeight * this._items.length; 126 var height = this._rowHeight * length;
132 this._hasVerticalScroll = height > maxHeight; 127 this._hasVerticalScroll = height > maxHeight;
133 this._element.style.height = Math.min(maxHeight, height) + 'px'; 128 this._element.style.height = Math.min(maxHeight, height) + 'px';
134 } 129 }
135 130
136 _updateWidth() { 131 /**
132 * @param {!UI.SuggestBox.Suggestions} items
133 */
134 _updateWidth(items) {
137 if (this._hasVerticalScroll) { 135 if (this._hasVerticalScroll) {
138 this._element.style.width = '100vw'; 136 this._element.style.width = '100vw';
139 return; 137 return;
140 } 138 }
139 if (!items.length)
140 return;
141 // If there are no scrollbars, set the width to the width of the largest row . 141 // If there are no scrollbars, set the width to the width of the largest row .
142 var maxIndex = 0; 142 var maxItem = items[0];
143 for (var i = 0; i < this._items.length; i++) { 143 for (var i = 1; i < items.length; i++) {
144 if (this._items[i].title.length > this._items[maxIndex].title.length) 144 if (items[i].title.length > maxItem.title.length)
145 maxIndex = i; 145 maxItem = items[i];
146 } 146 }
147 var element = /** @type {!Element} */ (this.itemElement(maxIndex)); 147 this._element.style.width = UI.measurePreferredSize(this.createElementForIte m(maxItem), this._element).width + 'px';
148 this._element.style.width = UI.measurePreferredSize(element, this._element). width + 'px';
149 } 148 }
150 149
151 /** 150 /**
152 * @param {!Event} event 151 * @param {!Event} event
153 */ 152 */
154 _onBoxMouseDown(event) { 153 _onBoxMouseDown(event) {
155 if (this._hideTimeoutId) { 154 if (this._hideTimeoutId) {
156 window.clearTimeout(this._hideTimeoutId); 155 window.clearTimeout(this._hideTimeoutId);
157 delete this._hideTimeoutId; 156 delete this._hideTimeoutId;
158 } 157 }
159 event.preventDefault(); 158 event.preventDefault();
160 } 159 }
161 160
162 _maybeHide() { 161 _maybeHide() {
163 if (!this._hideTimeoutId) 162 if (!this._hideTimeoutId)
164 this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0); 163 this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0);
165 } 164 }
166 165
167 /** 166 /**
168 * // FIXME: make SuggestBox work for multiple documents. 167 * // FIXME: make SuggestBox work for multiple documents.
169 * @suppressGlobalPropertiesCheck 168 * @suppressGlobalPropertiesCheck
170 */ 169 */
171 _show() { 170 _show() {
172 if (this.visible()) 171 if (this.visible())
173 return; 172 return;
174 this._bodyElement = document.body; 173 this._bodyElement = document.body;
175 this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true); 174 this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true);
176 this._overlay = new UI.SuggestBox.Overlay(); 175 this._overlay = new UI.SuggestBox.Overlay();
177 this._overlay.setContentElement(this._container); 176 this._overlay.setContentElement(this._container);
178 var measuringElement = this._createItemElement('1', '12'); 177 this._rowHeight =
179 this._viewport.element.appendChild(measuringElement); 178 UI.measurePreferredSize(this.createElementForItem({title: '1', subtitle: '12'}), this._element).height;
180 this._rowHeight = measuringElement.getBoundingClientRect().height;
181 measuringElement.remove();
182 } 179 }
183 180
184 hide() { 181 hide() {
185 if (!this.visible()) 182 if (!this.visible())
186 return; 183 return;
187 184
188 this._userInteracted = false; 185 this._userInteracted = false;
189 this._bodyElement.removeEventListener('mousedown', this._maybeHideBound, tru e); 186 this._bodyElement.removeEventListener('mousedown', this._maybeHideBound, tru e);
190 delete this._bodyElement; 187 delete this._bodyElement;
191 this._container.remove(); 188 this._container.remove();
192 this._overlay.dispose(); 189 this._overlay.dispose();
193 delete this._overlay; 190 delete this._overlay;
194 delete this._selectedElement;
195 this._selectedIndex = -1;
196 delete this._lastAnchorBox; 191 delete this._lastAnchorBox;
197 } 192 }
198 193
199 removeFromElement() { 194 removeFromElement() {
200 this.hide(); 195 this.hide();
201 } 196 }
202 197
203 /** 198 /**
204 * @param {boolean=} isIntermediateSuggestion 199 * @param {boolean=} isIntermediateSuggestion
205 * @return {boolean} 200 * @return {boolean}
206 */ 201 */
207 _applySuggestion(isIntermediateSuggestion) { 202 _applySuggestion(isIntermediateSuggestion) {
208 if (this._onlyCompletion) { 203 if (this._onlyCompletion) {
209 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermedi ateSuggestion); 204 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermedi ateSuggestion);
210 return true; 205 return true;
211 } 206 }
212 207
213 if (!this.visible() || !this._selectedElement) 208 if (!this.visible() || !this._list.selectedItem())
214 return false; 209 return false;
215 210
216 var suggestion = this._selectedElement.__fullValue; 211 var suggestion = this._list.selectedItem().title;
217 if (!suggestion) 212 if (!suggestion)
218 return false; 213 return false;
219 214
220 this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestio n); 215 this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestio n);
221 return true; 216 return true;
222 } 217 }
223 218
224 /** 219 /**
225 * @return {boolean} 220 * @return {boolean}
226 */ 221 */
227 acceptSuggestion() { 222 acceptSuggestion() {
228 var result = this._applySuggestion(); 223 var result = this._applySuggestion();
229 this.hide(); 224 this.hide();
230 if (!result) 225 if (!result)
231 return false; 226 return false;
232 227
233 this._suggestBoxDelegate.acceptSuggestion(); 228 this._suggestBoxDelegate.acceptSuggestion();
234 229
235 return true; 230 return true;
236 } 231 }
237 232
238 /** 233 /**
239 * @param {number} shift 234 * @override
240 * @param {boolean=} isCircular 235 * @return {?number}
241 * @return {boolean} is changed
242 */ 236 */
243 _selectClosest(shift, isCircular) { 237 fixedHeight() {
244 if (!this._length) 238 return this._rowHeight;
245 return false;
246
247 this._userInteracted = true;
248
249 if (this._selectedIndex === -1 && shift < 0)
250 shift += 1;
251
252 var index = this._selectedIndex + shift;
253
254 if (isCircular)
255 index = (this._length + index) % this._length;
256 else
257 index = Number.constrain(index, 0, this._length - 1);
258
259 this._selectItem(index);
260 return true;
261 } 239 }
262 240
263 /** 241 /**
264 * @param {!Event} event 242 * @override
265 */ 243 * @param {!UI.SuggestBox.Suggestion} item
266 _onItemMouseDown(event) {
267 this._selectedElement = event.currentTarget;
268 this.acceptSuggestion();
269 event.consume(true);
270 }
271
272 /**
273 * @param {string} query
274 * @param {string} title
275 * @param {string=} subtitle
276 * @param {string=} iconType
277 * @param {boolean=} isSecondary
278 * @return {!Element} 244 * @return {!Element}
279 */ 245 */
280 _createItemElement(query, title, subtitle, iconType, isSecondary) { 246 createElementForItem(item) {
247 var query = this._userEnteredText;
281 var element = createElementWithClass('div', 'suggest-box-content-item source -code'); 248 var element = createElementWithClass('div', 'suggest-box-content-item source -code');
282 if (iconType) { 249 if (item.iconType) {
283 var icon = UI.Icon.create(iconType, 'suggestion-icon'); 250 var icon = UI.Icon.create(item.iconType, 'suggestion-icon');
284 element.appendChild(icon); 251 element.appendChild(icon);
285 } 252 }
286 if (isSecondary) 253 if (item.isSecondary)
287 element.classList.add('secondary'); 254 element.classList.add('secondary');
288 element.tabIndex = -1; 255 element.tabIndex = -1;
289 var displayText = title.trimEnd(50 + query.length); 256 var displayText = item.title.trimEnd(50 + query.length);
290 257
291 var titleElement = element.createChild('span', 'suggestion-title'); 258 var titleElement = element.createChild('span', 'suggestion-title');
292 var index = displayText.toLowerCase().indexOf(query.toLowerCase()); 259 var index = displayText.toLowerCase().indexOf(query.toLowerCase());
293 if (index > 0) 260 if (index > 0)
294 titleElement.createChild('span').textContent = displayText.substring(0, in dex); 261 titleElement.createChild('span').textContent = displayText.substring(0, in dex);
295 if (index > -1) 262 if (index > -1)
296 titleElement.createChild('span', 'query').textContent = displayText.substr ing(index, index + query.length); 263 titleElement.createChild('span', 'query').textContent = displayText.substr ing(index, index + query.length);
297 titleElement.createChild('span').textContent = displayText.substring(index > -1 ? index + query.length : 0); 264 titleElement.createChild('span').textContent = displayText.substring(index > -1 ? index + query.length : 0);
298 titleElement.createChild('span', 'spacer'); 265 titleElement.createChild('span', 'spacer');
299 if (subtitle) { 266 if (item.subtitle) {
300 var subtitleElement = element.createChild('span', 'suggestion-subtitle'); 267 var subtitleElement = element.createChild('span', 'suggestion-subtitle');
301 subtitleElement.textContent = subtitle.trimEnd(15); 268 subtitleElement.textContent = item.subtitle.trimEnd(15);
302 } 269 }
303 element.__fullValue = title;
304 element.addEventListener('mousedown', this._onItemMouseDown.bind(this), fals e); 270 element.addEventListener('mousedown', this._onItemMouseDown.bind(this), fals e);
305 return element; 271 return element;
306 } 272 }
307 273
308 /** 274 /**
309 * @param {!UI.SuggestBox.Suggestions} items 275 * @override
310 * @param {string} userEnteredText 276 * @param {!UI.SuggestBox.Suggestion} item
311 * @param {function(number): !Promise<{detail:string, description:string}>=} a syncDetails 277 * @return {number}
312 */ 278 */
313 _updateItems(items, userEnteredText, asyncDetails) { 279 heightForItem(item) {
314 this._length = items.length; 280 return this._rowHeight;
315 this._asyncDetailsPromises.clear();
316 this._asyncDetailsCallback = asyncDetails;
317 this._elementList = [];
318 delete this._selectedElement;
319
320 this._userEnteredText = userEnteredText;
321 this._items = items;
322 } 281 }
323 282
324 /** 283 /**
325 * @param {number} index 284 * @override
326 * @return {!Promise<?{detail: string, description: string}>} 285 * @param {!UI.SuggestBox.Suggestion} item
286 * @return {boolean}
327 */ 287 */
328 _asyncDetails(index) { 288 isItemSelectable(item) {
329 if (!this._asyncDetailsCallback) 289 return true;
330 return Promise.resolve(/** @type {?{description: string, detail: string}} */ (null));
331 if (!this._asyncDetailsPromises.has(index))
332 this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(index));
333 return /** @type {!Promise<?{detail: string, description: string}>} */ (this ._asyncDetailsPromises.get(index));
334 } 290 }
335 291
336 /** 292 /**
293 * @override
294 * @param {?UI.SuggestBox.Suggestion} from
295 * @param {?UI.SuggestBox.Suggestion} to
296 * @param {?Element} fromElement
297 * @param {?Element} toElement
298 */
299 selectedItemChanged(from, to, fromElement, toElement) {
300 if (fromElement) {
301 fromElement.classList.remove('selected');
alph 2016/12/27 23:38:33 remove( ..., ... )
dgozman 2016/12/28 06:15:21 Done.
302 fromElement.classList.remove('force-white-icons');
303 }
304 if (toElement) {
305 toElement.classList.add('selected');
306 toElement.classList.add('force-white-icons');
307 }
308 if (to) {
alph 2016/12/27 23:38:33 if (!to) return;
dgozman 2016/12/28 06:15:21 Done.
309 this._detailsPopup.classList.add('hidden');
310 this._asyncDetails(to).then((details) => {
alph 2016/12/27 23:38:33 drop ()
dgozman 2016/12/28 06:15:21 Done.
311 if (this._list.selectedItem() === to)
312 this._showDetailsPopup(details);
313 });
314 this._applySuggestion(true);
315 }
316 }
317
318 /**
319 * @param {!Event} event
320 */
321 _onItemMouseDown(event) {
322 if (this._list.onClick(event)) {
alph 2016/12/27 23:38:33 if (!...) return
dgozman 2016/12/28 06:15:21 Done.
323 this.acceptSuggestion();
324 event.consume(true);
325 }
326 }
327
328 /**
329 * @param {!UI.SuggestBox.Suggestion} item
330 * @return {!Promise<?{detail: string, description: string}>}
331 */
332 _asyncDetails(item) {
333 if (!this._asyncDetailsCallback)
334 return Promise.resolve(/** @type {?{description: string, detail: string}} */ (null));
335 if (!this._asyncDetailsPromises.has(item))
336 this._asyncDetailsPromises.set(item, this._asyncDetailsCallback(item));
337 return /** @type {!Promise<?{detail: string, description: string}>} */ (this ._asyncDetailsPromises.get(item));
338 }
339
340 /**
337 * @param {?{detail: string, description: string}} details 341 * @param {?{detail: string, description: string}} details
338 */ 342 */
339 _showDetailsPopup(details) { 343 _showDetailsPopup(details) {
340 this._detailsPopup.removeChildren(); 344 this._detailsPopup.removeChildren();
341 if (!details) 345 if (!details)
342 return; 346 return;
343 this._detailsPopup.createChild('section', 'detail').createTextChild(details. detail); 347 this._detailsPopup.createChild('section', 'detail').createTextChild(details. detail);
344 this._detailsPopup.createChild('section', 'description').createTextChild(det ails.description); 348 this._detailsPopup.createChild('section', 'description').createTextChild(det ails.description);
345 this._detailsPopup.classList.remove('hidden'); 349 this._detailsPopup.classList.remove('hidden');
346 } 350 }
347 351
348 /** 352 /**
349 * @param {number} index
350 */
351 _selectItem(index) {
352 if (this._selectedElement) {
353 this._selectedElement.classList.remove('selected');
354 this._selectedElement.classList.remove('force-white-icons');
355 }
356
357 this._selectedIndex = index;
358 if (index < 0)
359 return;
360
361 this._selectedElement = this.itemElement(index);
362 this._selectedElement.classList.add('selected');
363 this._selectedElement.classList.add('force-white-icons');
364 this._detailsPopup.classList.add('hidden');
365 var elem = this._selectedElement;
366 this._asyncDetails(index).then(showDetails.bind(this), function() {});
367
368 this._viewport.scrollItemIntoView(index);
369 this._applySuggestion(true);
370
371 /**
372 * @param {?{detail: string, description: string}} details
373 * @this {UI.SuggestBox}
374 */
375 function showDetails(details) {
376 if (elem === this._selectedElement)
377 this._showDetailsPopup(details);
378 }
379 }
380
381 /**
382 * @param {!UI.SuggestBox.Suggestions} completions 353 * @param {!UI.SuggestBox.Suggestions} completions
383 * @param {boolean} canShowForSingleItem 354 * @param {boolean} canShowForSingleItem
384 * @param {string} userEnteredText 355 * @param {string} userEnteredText
385 * @return {boolean} 356 * @return {boolean}
386 */ 357 */
387 _canShowBox(completions, canShowForSingleItem, userEnteredText) { 358 _canShowBox(completions, canShowForSingleItem, userEnteredText) {
388 if (!completions || !completions.length) 359 if (!completions || !completions.length)
389 return false; 360 return false;
390 361
391 if (completions.length > 1) 362 if (completions.length > 1)
392 return true; 363 return true;
393 364
394 if (!completions[0].title.startsWith(userEnteredText)) 365 if (!completions[0].title.startsWith(userEnteredText))
395 return true; 366 return true;
396 367
397 // Do not show a single suggestion if it is the same as user-entered query, even if allowed to show single-item suggest boxes. 368 // Do not show a single suggestion if it is the same as user-entered query, even if allowed to show single-item suggest boxes.
398 return canShowForSingleItem && completions[0].title !== userEnteredText; 369 return canShowForSingleItem && completions[0].title !== userEnteredText;
399 } 370 }
400 371
401 _ensureRowCountPerViewport() {
402 if (this._rowCountPerViewport)
403 return;
404 if (!this._items.length)
405 return;
406
407 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRect() .height / this._rowHeight);
408 }
409
410 /** 372 /**
411 * @param {!AnchorBox} anchorBox 373 * @param {!AnchorBox} anchorBox
412 * @param {!UI.SuggestBox.Suggestions} completions 374 * @param {!UI.SuggestBox.Suggestions} completions
413 * @param {boolean} selectHighestPriority 375 * @param {boolean} selectHighestPriority
414 * @param {boolean} canShowForSingleItem 376 * @param {boolean} canShowForSingleItem
415 * @param {string} userEnteredText 377 * @param {string} userEnteredText
416 * @param {function(number): !Promise<{detail:string, description:string}>=} a syncDetails 378 * @param {function(number): !Promise<{detail:string, description:string}>=} a syncDetails
417 */ 379 */
418 updateSuggestions( 380 updateSuggestions(
419 anchorBox, 381 anchorBox,
420 completions, 382 completions,
421 selectHighestPriority, 383 selectHighestPriority,
422 canShowForSingleItem, 384 canShowForSingleItem,
423 userEnteredText, 385 userEnteredText,
424 asyncDetails) { 386 asyncDetails) {
425 delete this._onlyCompletion; 387 delete this._onlyCompletion;
426 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) { 388 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) {
427 this._updateItems(completions, userEnteredText, asyncDetails); 389 this._asyncDetailsPromises.clear();
390 if (asyncDetails)
391 this._asyncDetailsCallback = (item) => asyncDetails.call(null, completio ns.indexOf(item));
alph 2016/12/27 23:38:33 item => asyncDetails(completions.indexOf(item));
dgozman 2016/12/28 06:15:21 Done.
392 else
393 this._asyncDetailsCallback = null;
394 this._userEnteredText = userEnteredText;
395
428 this._show(); 396 this._show();
429 this._updateBoxPosition(anchorBox); 397 this._updateBoxPosition(anchorBox, completions.length);
430 this._updateWidth(); 398 this._updateWidth(completions);
431 this._viewport.refresh(); 399
400 this._list.reset(this, false);
401 this._list.replaceAllItems(completions);
402
432 var highestPriorityItem = -1; 403 var highestPriorityItem = -1;
433 if (selectHighestPriority) { 404 if (selectHighestPriority) {
434 var highestPriority = -Infinity; 405 var highestPriority = -Infinity;
435 for (var i = 0; i < completions.length; i++) { 406 for (var i = 0; i < completions.length; i++) {
436 var priority = completions[i].priority || 0; 407 var priority = completions[i].priority || 0;
437 if (highestPriority < priority) { 408 if (highestPriority < priority) {
438 highestPriority = priority; 409 highestPriority = priority;
439 highestPriorityItem = i; 410 highestPriorityItem = i;
440 } 411 }
441 } 412 }
442 } 413 }
443 this._selectItem(highestPriorityItem); 414 this._list.selectItemAtIndex(highestPriorityItem, true);
444 delete this._rowCountPerViewport;
445 } else { 415 } else {
446 if (completions.length === 1) { 416 if (completions.length === 1) {
447 this._onlyCompletion = completions[0].title; 417 this._onlyCompletion = completions[0].title;
448 this._applySuggestion(true); 418 this._applySuggestion(true);
449 } 419 }
450 this.hide(); 420 this.hide();
451 } 421 }
452 } 422 }
453 423
454 /** 424 /**
455 * @param {!KeyboardEvent} event 425 * @param {!KeyboardEvent} event
456 * @return {boolean} 426 * @return {boolean}
457 */ 427 */
458 keyPressed(event) { 428 keyPressed(event) {
459 switch (event.key) { 429 if (this._list.onKeyDown(event)) {
460 case 'ArrowUp': 430 this._userInteracted = true;
461 return this.upKeyPressed(); 431 return true;
462 case 'ArrowDown':
463 return this.downKeyPressed();
464 case 'PageUp':
465 return this.pageUpKeyPressed();
466 case 'PageDown':
467 return this.pageDownKeyPressed();
468 case 'Enter':
469 return this.enterKeyPressed();
470 } 432 }
433 if (event.key === 'Enter')
434 return this.enterKeyPressed();
471 return false; 435 return false;
472 } 436 }
473 437
474 /** 438 /**
475 * @return {boolean} 439 * @return {boolean}
476 */ 440 */
477 upKeyPressed() {
478 return this._selectClosest(-1, true);
479 }
480
481 /**
482 * @return {boolean}
483 */
484 downKeyPressed() {
485 return this._selectClosest(1, true);
486 }
487
488 /**
489 * @return {boolean}
490 */
491 pageUpKeyPressed() {
492 this._ensureRowCountPerViewport();
493 return this._selectClosest(-this._rowCountPerViewport, false);
494 }
495
496 /**
497 * @return {boolean}
498 */
499 pageDownKeyPressed() {
500 this._ensureRowCountPerViewport();
501 return this._selectClosest(this._rowCountPerViewport, false);
502 }
503
504 /**
505 * @return {boolean}
506 */
507 enterKeyPressed() { 441 enterKeyPressed() {
508 if (!this._userInteracted && this._captureEnter) 442 if (!this._userInteracted && this._captureEnter)
509 return false; 443 return false;
510 444
511 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion; 445 var hasSelectedItem = !!this._list.selectedItem() || this._onlyCompletion;
512 this.acceptSuggestion(); 446 this.acceptSuggestion();
513 447
514 // Report the event as non-handled if there is no selected item, 448 // Report the event as non-handled if there is no selected item,
515 // to commit the input or handle it otherwise. 449 // to commit the input or handle it otherwise.
516 return hasSelectedItem; 450 return hasSelectedItem;
517 } 451 }
518
519 /**
520 * @override
521 * @param {number} index
522 * @return {number}
523 */
524 fastItemHeight(index) {
525 return this._rowHeight;
526 }
527
528 /**
529 * @override
530 * @return {number}
531 */
532 itemCount() {
533 return this._items.length;
534 }
535
536 /**
537 * @override
538 * @param {number} index
539 * @return {?Element}
540 */
541 itemElement(index) {
542 if (!this._elementList[index]) {
543 this._elementList[index] = this._createItemElement(
544 this._userEnteredText, this._items[index].title, this._items[index].su btitle, this._items[index].iconType,
545 this._items[index].isSecondary);
546 }
547 return this._elementList[index];
548 }
549 }; 452 };
550 453
551 /** 454 /**
552 * @typedef {!Array.<{title: string, subtitle: (string|undefined), iconType: (st ring|undefined), priority: (number|undefined), isSecondary: (boolean|undefined)} >} 455 * @typedef {!{title: string, subtitle: (string|undefined), iconType: (string|un defined), priority: (number|undefined), isSecondary: (boolean|undefined)}}
456 */
457 UI.SuggestBox.Suggestion;
458
459 /**
460 * @typedef {!Array<!UI.SuggestBox.Suggestion>}
553 */ 461 */
554 UI.SuggestBox.Suggestions; 462 UI.SuggestBox.Suggestions;
555 463
556 /** 464 /**
557 * @unrestricted 465 * @unrestricted
558 */ 466 */
559 UI.SuggestBox.Overlay = class { 467 UI.SuggestBox.Overlay = class {
560 /** 468 /**
561 * // FIXME: make SuggestBox work for multiple documents. 469 * // FIXME: make SuggestBox work for multiple documents.
562 * @suppressGlobalPropertiesCheck 470 * @suppressGlobalPropertiesCheck
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
609 this.element.style.left = containerBox.x + 'px'; 517 this.element.style.left = containerBox.x + 'px';
610 this.element.style.top = containerBox.y + 'px'; 518 this.element.style.top = containerBox.y + 'px';
611 this.element.style.height = containerBox.height + 'px'; 519 this.element.style.height = containerBox.height + 'px';
612 this.element.style.width = containerBox.width + 'px'; 520 this.element.style.width = containerBox.width + 'px';
613 } 521 }
614 522
615 dispose() { 523 dispose() {
616 this.element.remove(); 524 this.element.remove();
617 } 525 }
618 }; 526 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698