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

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

Issue 2392963004: DevTools: Display SuggestBox with a Viewport control (Closed)
Patch Set: this._hasScrollBars Created 4 years, 2 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
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/ui/suggestBox.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 applySuggestion: function(suggestion, isIntermediateSuggestion) { }, 43 applySuggestion: function(suggestion, isIntermediateSuggestion) { },
44 44
45 /** 45 /**
46 * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false. 46 * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false.
47 */ 47 */
48 acceptSuggestion: function() { }, 48 acceptSuggestion: function() { },
49 } 49 }
50 50
51 /** 51 /**
52 * @constructor 52 * @constructor
53 * @implements {WebInspector.StaticViewportControl.Provider}
53 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate 54 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate
54 * @param {number=} maxItemsHeight 55 * @param {number=} maxItemsHeight
55 * @param {boolean=} captureEnter 56 * @param {boolean=} captureEnter
56 */ 57 */
57 WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight, captureEn ter) 58 WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight, captureEn ter)
58 { 59 {
59 this._suggestBoxDelegate = suggestBoxDelegate; 60 this._suggestBoxDelegate = suggestBoxDelegate;
60 this._length = 0; 61 this._length = 0;
61 this._selectedIndex = -1; 62 this._selectedIndex = -1;
62 this._selectedElement = null; 63 this._selectedElement = null;
63 this._maxItemsHeight = maxItemsHeight; 64 this._maxItemsHeight = maxItemsHeight;
64 this._maybeHideBound = this._maybeHide.bind(this); 65 this._maybeHideBound = this._maybeHide.bind(this);
65 this._container = createElementWithClass("div", "suggest-box-container"); 66 this._container = createElementWithClass("div", "suggest-box-container");
66 this._element = this._container.createChild("div", "suggest-box"); 67 this._viewport = new WebInspector.StaticViewportControl(this);
68 this._element = this._viewport.element;
69 this._element.classList.add("suggest-box");
70 this._container.appendChild(this._element);
67 this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this), true); 71 this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this), true);
68 this._detailsPopup = this._container.createChild("div", "suggest-box details -popup monospace"); 72 this._detailsPopup = this._container.createChild("div", "suggest-box details -popup monospace");
69 this._detailsPopup.classList.add("hidden"); 73 this._detailsPopup.classList.add("hidden");
70 this._asyncDetailsCallback = null; 74 this._asyncDetailsCallback = null;
71 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */ 75 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */
72 this._asyncDetailsPromises = new Map(); 76 this._asyncDetailsPromises = new Map();
73 this._userInteracted = false; 77 this._userInteracted = false;
74 this._captureEnter = captureEnter; 78 this._captureEnter = captureEnter;
79 /** @type {!Array<!Element>} */
80 this._elementList = [];
81 this._rowHeight = 17;
82 this._viewportWidth = "100vw";
83 this._hasScrollBars = false;
75 } 84 }
76 85
77 /** 86 /**
78 * @typedef {!Array.<{title: string, className: (string|undefined)}>} 87 * @typedef {!Array.<{title: string, className: (string|undefined)}>}
79 */ 88 */
80 WebInspector.SuggestBox.Suggestions; 89 WebInspector.SuggestBox.Suggestions;
81 90
82 WebInspector.SuggestBox.prototype = { 91 WebInspector.SuggestBox.prototype = {
83 /** 92 /**
84 * @return {boolean} 93 * @return {boolean}
(...skipping 10 matching lines...) Expand all
95 { 104 {
96 this._updateBoxPosition(anchorBox); 105 this._updateBoxPosition(anchorBox);
97 }, 106 },
98 107
99 /** 108 /**
100 * @param {!AnchorBox} anchorBox 109 * @param {!AnchorBox} anchorBox
101 */ 110 */
102 _updateBoxPosition: function(anchorBox) 111 _updateBoxPosition: function(anchorBox)
103 { 112 {
104 console.assert(this._overlay); 113 console.assert(this._overlay);
105 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox)) 114 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this ._lastItemCount === this.itemCount())
106 return; 115 return;
116 this._lastItemCount = this.itemCount();
107 this._lastAnchorBox = anchorBox; 117 this._lastAnchorBox = anchorBox;
108 118
109 // Position relative to main DevTools element. 119 // Position relative to main DevTools element.
110 var container = WebInspector.Dialog.modalHostView().element; 120 var container = WebInspector.Dialog.modalHostView().element;
111 anchorBox = anchorBox.relativeToElement(container); 121 anchorBox = anchorBox.relativeToElement(container);
112 var totalHeight = container.offsetHeight; 122 var totalHeight = container.offsetHeight;
113 var aboveHeight = anchorBox.y; 123 var aboveHeight = anchorBox.y;
114 var underHeight = totalHeight - anchorBox.y - anchorBox.height; 124 var underHeight = totalHeight - anchorBox.y - anchorBox.height;
115 125
116 this._overlay.setLeftOffset(anchorBox.x); 126 this._overlay.setLeftOffset(anchorBox.x);
117 127
118 var under = underHeight >= aboveHeight; 128 var under = underHeight >= aboveHeight;
119 if (under) 129 if (under)
120 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true ); 130 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true );
121 else 131 else
122 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false); 132 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false);
123 133
124 /** const */ var rowHeight = 17; 134 var spacer = 6;
125 /** const */ var spacer = 6; 135 var maxHeight = this._maxItemsHeight ? this._maxItemsHeight * this._rowH eight : Math.max(underHeight, aboveHeight) - spacer;
126 var maxHeight = this._maxItemsHeight ? this._maxItemsHeight * rowHeight : Math.max(underHeight, aboveHeight) - spacer; 136 var height = this._rowHeight * this._elementList.length;
127 this._element.style.maxHeight = maxHeight + "px"; 137 this._hasScrollBars = height > maxHeight;
138 this._element.style.height = Math.min(maxHeight, height) + "px";
139 },
140
141 _updateWidth: function()
142 {
143 if (this._hasScrollBars) {
144 this._element.style.width = "100vw";
145 return;
146 }
147 // If there are no scrollbars, set the width to the width of the largest row.
148 // Perform N sync layouts inside a layout boundary of a small size.
149 var width = 0;
150 for (var i = 0; i < this._elementList.length; i++)
lushnikov 2016/10/10 20:49:24 "I can find the largest size one and measure it in
einbinder 2016/10/11 00:18:16 Done.
151 width = Math.max(width, WebInspector.measurePreferredSize(this._elem entList[i], this._element).width);
152 this._element.style.width = width + "px";
128 }, 153 },
129 154
130 /** 155 /**
131 * @param {!Event} event 156 * @param {!Event} event
132 */ 157 */
133 _onBoxMouseDown: function(event) 158 _onBoxMouseDown: function(event)
134 { 159 {
135 if (this._hideTimeoutId) { 160 if (this._hideTimeoutId) {
136 window.clearTimeout(this._hideTimeoutId); 161 window.clearTimeout(this._hideTimeoutId);
137 delete this._hideTimeoutId; 162 delete this._hideTimeoutId;
(...skipping 12 matching lines...) Expand all
150 * @suppressGlobalPropertiesCheck 175 * @suppressGlobalPropertiesCheck
151 */ 176 */
152 _show: function() 177 _show: function()
153 { 178 {
154 if (this.visible()) 179 if (this.visible())
155 return; 180 return;
156 this._bodyElement = document.body; 181 this._bodyElement = document.body;
157 this._bodyElement.addEventListener("mousedown", this._maybeHideBound, tr ue); 182 this._bodyElement.addEventListener("mousedown", this._maybeHideBound, tr ue);
158 this._overlay = new WebInspector.SuggestBox.Overlay(); 183 this._overlay = new WebInspector.SuggestBox.Overlay();
159 this._overlay.setContentElement(this._container); 184 this._overlay.setContentElement(this._container);
185 var measuringElement = this._createItemElement("1", "12");
186 this._viewport.element.appendChild(measuringElement);
187 this._rowHeight = measuringElement.getBoundingClientRect().height;
188 measuringElement.remove();
160 }, 189 },
161 190
162 hide: function() 191 hide: function()
163 { 192 {
164 if (!this.visible()) 193 if (!this.visible())
165 return; 194 return;
166 195
167 this._userInteracted = false; 196 this._userInteracted = false;
168 this._bodyElement.removeEventListener("mousedown", this._maybeHideBound, true); 197 this._bodyElement.removeEventListener("mousedown", this._maybeHideBound, true);
169 delete this._bodyElement; 198 delete this._bodyElement;
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
249 _onItemMouseDown: function(event) 278 _onItemMouseDown: function(event)
250 { 279 {
251 this._selectedElement = event.currentTarget; 280 this._selectedElement = event.currentTarget;
252 this.acceptSuggestion(); 281 this.acceptSuggestion();
253 event.consume(true); 282 event.consume(true);
254 }, 283 },
255 284
256 /** 285 /**
257 * @param {string} prefix 286 * @param {string} prefix
258 * @param {string} text 287 * @param {string} text
259 * @param {string|undefined} className 288 * @param {string=} className
260 * @param {number} index 289 * @return {!Element}
261 */ 290 */
262 _createItemElement: function(prefix, text, className, index) 291 _createItemElement: function(prefix, text, className)
263 { 292 {
264 var element = createElementWithClass("div", "suggest-box-content-item so urce-code " + (className || "")); 293 var element = createElementWithClass("div", "suggest-box-content-item so urce-code " + (className || ""));
265 element.tabIndex = -1; 294 element.tabIndex = -1;
266 if (prefix && prefix.length && !text.indexOf(prefix)) { 295 if (prefix && prefix.length && !text.indexOf(prefix)) {
267 element.createChild("span", "prefix").textContent = prefix; 296 element.createChild("span", "prefix").textContent = prefix;
268 element.createChild("span", "suffix").textContent = text.substring(p refix.length).trimEnd(50); 297 element.createChild("span", "suffix").textContent = text.substring(p refix.length).trimEnd(50);
269 } else { 298 } else {
270 element.createChild("span", "suffix").textContent = text.trimEnd(50) ; 299 element.createChild("span", "suffix").textContent = text.trimEnd(50) ;
271 } 300 }
272 element.__fullValue = text; 301 element.__fullValue = text;
273 element.createChild("span", "spacer"); 302 element.createChild("span", "spacer");
274 element.addEventListener("mousedown", this._onItemMouseDown.bind(this), false); 303 element.addEventListener("mousedown", this._onItemMouseDown.bind(this), false);
lushnikov 2016/10/10 20:49:24 one event listener on the container should be more
275 return element; 304 return element;
276 }, 305 },
277 306
278 /** 307 /**
279 * @param {!WebInspector.SuggestBox.Suggestions} items 308 * @param {!WebInspector.SuggestBox.Suggestions} items
280 * @param {string} userEnteredText 309 * @param {string} userEnteredText
281 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails 310 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
282 */ 311 */
283 _updateItems: function(items, userEnteredText, asyncDetails) 312 _updateItems: function(items, userEnteredText, asyncDetails)
284 { 313 {
285 this._length = items.length; 314 this._length = items.length;
286 this._asyncDetailsPromises.clear(); 315 this._asyncDetailsPromises.clear();
287 this._asyncDetailsCallback = asyncDetails; 316 this._asyncDetailsCallback = asyncDetails;
288 this._element.removeChildren(); 317 this._elementList = [];
289 delete this._selectedElement; 318 delete this._selectedElement;
290 319
291 for (var i = 0; i < items.length; ++i) { 320 for (var i = 0; i < items.length; ++i)
292 var item = items[i]; 321 this._elementList.push(this._createItemElement(userEnteredText, item s[i].title, items[i].className));
lushnikov 2016/10/10 20:49:24 lazy creation should work faster
einbinder 2016/10/11 00:18:16 Done.
293 var currentItemElement = this._createItemElement(userEnteredText, it em.title, item.className, i);
294 this._element.appendChild(currentItemElement);
295 }
296 }, 322 },
297 323
298 /** 324 /**
299 * @param {number} index 325 * @param {number} index
300 * @return {!Promise<?{detail: string, description: string}>} 326 * @return {!Promise<?{detail: string, description: string}>}
301 */ 327 */
302 _asyncDetails: function(index) 328 _asyncDetails: function(index)
303 { 329 {
304 if (!this._asyncDetailsCallback) 330 if (!this._asyncDetailsCallback)
305 return Promise.resolve(/** @type {?{description: string, detail: str ing}} */(null)); 331 return Promise.resolve(/** @type {?{description: string, detail: str ing}} */(null));
(...skipping 21 matching lines...) Expand all
327 */ 353 */
328 _selectItem: function(index, scrollIntoView) 354 _selectItem: function(index, scrollIntoView)
329 { 355 {
330 if (this._selectedElement) 356 if (this._selectedElement)
331 this._selectedElement.classList.remove("selected"); 357 this._selectedElement.classList.remove("selected");
332 358
333 this._selectedIndex = index; 359 this._selectedIndex = index;
334 if (index < 0) 360 if (index < 0)
335 return; 361 return;
336 362
337 this._selectedElement = this._element.children[index]; 363 this._selectedElement = this._elementList[index];
338 this._selectedElement.classList.add("selected"); 364 this._selectedElement.classList.add("selected");
339 this._detailsPopup.classList.add("hidden"); 365 this._detailsPopup.classList.add("hidden");
340 var elem = this._selectedElement; 366 var elem = this._selectedElement;
341 this._asyncDetails(index).then(showDetails.bind(this), function(){}); 367 this._asyncDetails(index).then(showDetails.bind(this), function(){});
342 368
343 if (scrollIntoView) 369 if (scrollIntoView)
344 this._selectedElement.scrollIntoViewIfNeeded(false); 370 this._viewport.scrollItemIntoView(index);
345 371
346 /** 372 /**
347 * @param {?{detail: string, description: string}} details 373 * @param {?{detail: string, description: string}} details
348 * @this {WebInspector.SuggestBox} 374 * @this {WebInspector.SuggestBox}
349 */ 375 */
350 function showDetails(details) 376 function showDetails(details)
351 { 377 {
352 if (elem === this._selectedElement) 378 if (elem === this._selectedElement)
353 this._showDetailsPopup(details); 379 this._showDetailsPopup(details);
354 } 380 }
(...skipping 13 matching lines...) Expand all
368 return true; 394 return true;
369 395
370 // Do not show a single suggestion if it is the same as user-entered pre fix, even if allowed to show single-item suggest boxes. 396 // Do not show a single suggestion if it is the same as user-entered pre fix, even if allowed to show single-item suggest boxes.
371 return canShowForSingleItem && completions[0].title !== userEnteredText; 397 return canShowForSingleItem && completions[0].title !== userEnteredText;
372 }, 398 },
373 399
374 _ensureRowCountPerViewport: function() 400 _ensureRowCountPerViewport: function()
375 { 401 {
376 if (this._rowCountPerViewport) 402 if (this._rowCountPerViewport)
377 return; 403 return;
378 if (!this._element.firstChild) 404 if (!this._elementList.length)
379 return; 405 return;
380 406
381 this._rowCountPerViewport = Math.floor(this._element.offsetHeight / this ._element.firstChild.offsetHeight); 407 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRe ct().height / this._rowHeight);
382 }, 408 },
383 409
384 /** 410 /**
385 * @param {!AnchorBox} anchorBox 411 * @param {!AnchorBox} anchorBox
386 * @param {!WebInspector.SuggestBox.Suggestions} completions 412 * @param {!WebInspector.SuggestBox.Suggestions} completions
387 * @param {number} selectedIndex 413 * @param {number} selectedIndex
388 * @param {boolean} canShowForSingleItem 414 * @param {boolean} canShowForSingleItem
389 * @param {string} userEnteredText 415 * @param {string} userEnteredText
390 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails 416 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
391 */ 417 */
392 updateSuggestions: function(anchorBox, completions, selectedIndex, canShowFo rSingleItem, userEnteredText, asyncDetails) 418 updateSuggestions: function(anchorBox, completions, selectedIndex, canShowFo rSingleItem, userEnteredText, asyncDetails)
393 { 419 {
394 delete this._onlyCompletion; 420 delete this._onlyCompletion;
395 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText) ) { 421 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText) ) {
396 this._updateItems(completions, userEnteredText, asyncDetails); 422 this._updateItems(completions, userEnteredText, asyncDetails);
397 this._show(); 423 this._show();
398 this._updateBoxPosition(anchorBox); 424 this._updateBoxPosition(anchorBox);
425 this._updateWidth();
426 this._viewport.refresh();
399 this._selectItem(selectedIndex, selectedIndex > 0); 427 this._selectItem(selectedIndex, selectedIndex > 0);
400 delete this._rowCountPerViewport; 428 delete this._rowCountPerViewport;
401 } else { 429 } else {
402 if (completions.length === 1) 430 if (completions.length === 1)
403 this._onlyCompletion = completions[0].title; 431 this._onlyCompletion = completions[0].title;
404 this.hide(); 432 this.hide();
405 } 433 }
406 }, 434 },
407 435
408 /** 436 /**
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
467 { 495 {
468 if (!this._userInteracted && this._captureEnter) 496 if (!this._userInteracted && this._captureEnter)
469 return false; 497 return false;
470 498
471 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion; 499 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion;
472 this.acceptSuggestion(); 500 this.acceptSuggestion();
473 501
474 // Report the event as non-handled if there is no selected item, 502 // Report the event as non-handled if there is no selected item,
475 // to commit the input or handle it otherwise. 503 // to commit the input or handle it otherwise.
476 return hasSelectedItem; 504 return hasSelectedItem;
505 },
506
507 /**
508 * @override
509 * @param {number} index
510 * @return {number}
511 */
512 fastItemHeight: function(index)
513 {
514 return this._rowHeight;
515 },
516
517 /**
518 * @override
519 * @return {number}
520 */
521 itemCount: function()
522 {
523 return this._elementList.length;
524 },
525
526 /**
527 * @override
528 * @param {number} index
529 * @return {?Element}
530 */
531 itemElement: function(index)
532 {
533 return this._elementList[index];
477 } 534 }
478 } 535 }
479 536
480 /** 537 /**
481 * @constructor 538 * @constructor
482 * // FIXME: make SuggestBox work for multiple documents. 539 * // FIXME: make SuggestBox work for multiple documents.
483 * @suppressGlobalPropertiesCheck 540 * @suppressGlobalPropertiesCheck
484 */ 541 */
485 WebInspector.SuggestBox.Overlay = function() 542 WebInspector.SuggestBox.Overlay = function()
486 { 543 {
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 this.element.style.top = containerBox.y + "px"; 594 this.element.style.top = containerBox.y + "px";
538 this.element.style.height = containerBox.height + "px"; 595 this.element.style.height = containerBox.height + "px";
539 this.element.style.width = containerBox.width + "px"; 596 this.element.style.width = containerBox.width + "px";
540 }, 597 },
541 598
542 dispose: function() 599 dispose: function()
543 { 600 {
544 this.element.remove(); 601 this.element.remove();
545 } 602 }
546 } 603 }
OLDNEW
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/ui/suggestBox.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698