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

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