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

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

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month 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
11 * copyright notice, this list of conditions and the following disclaimer 11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the 12 * in the documentation and/or other materials provided with the
13 * distribution. 13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its 14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from 15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission. 16 * this software without specific prior written permission.
17 * 17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */ 29 */
30
31 /** 30 /**
32 * @interface 31 * @interface
33 */ 32 */
34 WebInspector.SuggestBoxDelegate = function() 33 WebInspector.SuggestBoxDelegate = function() {};
35 {
36 };
37 34
38 WebInspector.SuggestBoxDelegate.prototype = { 35 WebInspector.SuggestBoxDelegate.prototype = {
39 /** 36 /**
40 * @param {string} suggestion 37 * @param {string} suggestion
41 * @param {boolean=} isIntermediateSuggestion 38 * @param {boolean=} isIntermediateSuggestion
42 */ 39 */
43 applySuggestion: function(suggestion, isIntermediateSuggestion) { }, 40 applySuggestion: function(suggestion, isIntermediateSuggestion) {},
44 41
45 /** 42 /**
46 * acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false. 43 * acceptSuggestion will be always called after call to applySuggestion with i sIntermediateSuggestion being equal to false.
47 */ 44 */
48 acceptSuggestion: function() { }, 45 acceptSuggestion: function() {},
49 }; 46 };
50 47
51 /** 48 /**
52 * @constructor
53 * @implements {WebInspector.StaticViewportControl.Provider} 49 * @implements {WebInspector.StaticViewportControl.Provider}
54 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate 50 * @unrestricted
55 * @param {number=} maxItemsHeight
56 * @param {boolean=} captureEnter
57 */ 51 */
58 WebInspector.SuggestBox = function(suggestBoxDelegate, maxItemsHeight, captureEn ter) 52 WebInspector.SuggestBox = class {
59 { 53 /**
54 * @param {!WebInspector.SuggestBoxDelegate} suggestBoxDelegate
55 * @param {number=} maxItemsHeight
56 * @param {boolean=} captureEnter
57 */
58 constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) {
60 this._suggestBoxDelegate = suggestBoxDelegate; 59 this._suggestBoxDelegate = suggestBoxDelegate;
61 this._length = 0; 60 this._length = 0;
62 this._selectedIndex = -1; 61 this._selectedIndex = -1;
63 this._selectedElement = null; 62 this._selectedElement = null;
64 this._maxItemsHeight = maxItemsHeight; 63 this._maxItemsHeight = maxItemsHeight;
65 this._maybeHideBound = this._maybeHide.bind(this); 64 this._maybeHideBound = this._maybeHide.bind(this);
66 this._container = createElementWithClass("div", "suggest-box-container"); 65 this._container = createElementWithClass('div', 'suggest-box-container');
67 this._viewport = new WebInspector.StaticViewportControl(this); 66 this._viewport = new WebInspector.StaticViewportControl(this);
68 this._element = this._viewport.element; 67 this._element = this._viewport.element;
69 this._element.classList.add("suggest-box"); 68 this._element.classList.add('suggest-box');
70 this._container.appendChild(this._element); 69 this._container.appendChild(this._element);
71 this._element.addEventListener("mousedown", this._onBoxMouseDown.bind(this), true); 70 this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true);
72 this._detailsPopup = this._container.createChild("div", "suggest-box details -popup monospace"); 71 this._detailsPopup = this._container.createChild('div', 'suggest-box details -popup monospace');
73 this._detailsPopup.classList.add("hidden"); 72 this._detailsPopup.classList.add('hidden');
74 this._asyncDetailsCallback = null; 73 this._asyncDetailsCallback = null;
75 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */ 74 /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */
76 this._asyncDetailsPromises = new Map(); 75 this._asyncDetailsPromises = new Map();
77 this._userInteracted = false; 76 this._userInteracted = false;
78 this._captureEnter = captureEnter; 77 this._captureEnter = captureEnter;
79 /** @type {!Array<!Element>} */ 78 /** @type {!Array<!Element>} */
80 this._elementList = []; 79 this._elementList = [];
81 this._rowHeight = 17; 80 this._rowHeight = 17;
82 this._viewportWidth = "100vw"; 81 this._viewportWidth = '100vw';
83 this._hasVerticalScroll = false; 82 this._hasVerticalScroll = false;
84 this._userEnteredText = ""; 83 this._userEnteredText = '';
85 /** @type {!WebInspector.SuggestBox.Suggestions} */ 84 /** @type {!WebInspector.SuggestBox.Suggestions} */
86 this._items = []; 85 this._items = [];
86 }
87
88 /**
89 * @return {boolean}
90 */
91 visible() {
92 return !!this._container.parentElement;
93 }
94
95 /**
96 * @param {!AnchorBox} anchorBox
97 */
98 setPosition(anchorBox) {
99 this._updateBoxPosition(anchorBox);
100 }
101
102 /**
103 * @param {!AnchorBox} anchorBox
104 */
105 _updateBoxPosition(anchorBox) {
106 console.assert(this._overlay);
107 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._la stItemCount === this.itemCount())
108 return;
109 this._lastItemCount = this.itemCount();
110 this._lastAnchorBox = anchorBox;
111
112 // Position relative to main DevTools element.
113 var container = WebInspector.Dialog.modalHostView().element;
114 anchorBox = anchorBox.relativeToElement(container);
115 var totalHeight = container.offsetHeight;
116 var aboveHeight = anchorBox.y;
117 var underHeight = totalHeight - anchorBox.y - anchorBox.height;
118
119 this._overlay.setLeftOffset(anchorBox.x);
120
121 var under = underHeight >= aboveHeight;
122 if (under)
123 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true);
124 else
125 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false);
126
127 var spacer = 6;
128 var maxHeight = Math.min(
129 Math.max(underHeight, aboveHeight) - spacer, this._maxItemsHeight ? this ._maxItemsHeight * this._rowHeight : 0);
130 var height = this._rowHeight * this._items.length;
131 this._hasVerticalScroll = height > maxHeight;
132 this._element.style.height = Math.min(maxHeight, height) + 'px';
133 }
134
135 _updateWidth() {
136 if (this._hasVerticalScroll) {
137 this._element.style.width = '100vw';
138 return;
139 }
140 // If there are no scrollbars, set the width to the width of the largest row .
141 var maxIndex = 0;
142 for (var i = 0; i < this._items.length; i++) {
143 if (this._items[i].title.length > this._items[maxIndex].title.length)
144 maxIndex = i;
145 }
146 var element = /** @type {!Element} */ (this.itemElement(maxIndex));
147 this._element.style.width = WebInspector.measurePreferredSize(element, this. _element).width + 'px';
148 }
149
150 /**
151 * @param {!Event} event
152 */
153 _onBoxMouseDown(event) {
154 if (this._hideTimeoutId) {
155 window.clearTimeout(this._hideTimeoutId);
156 delete this._hideTimeoutId;
157 }
158 event.preventDefault();
159 }
160
161 _maybeHide() {
162 if (!this._hideTimeoutId)
163 this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0);
164 }
165
166 /**
167 * // FIXME: make SuggestBox work for multiple documents.
168 * @suppressGlobalPropertiesCheck
169 */
170 _show() {
171 if (this.visible())
172 return;
173 this._bodyElement = document.body;
174 this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true);
175 this._overlay = new WebInspector.SuggestBox.Overlay();
176 this._overlay.setContentElement(this._container);
177 var measuringElement = this._createItemElement('1', '12');
178 this._viewport.element.appendChild(measuringElement);
179 this._rowHeight = measuringElement.getBoundingClientRect().height;
180 measuringElement.remove();
181 }
182
183 hide() {
184 if (!this.visible())
185 return;
186
187 this._userInteracted = false;
188 this._bodyElement.removeEventListener('mousedown', this._maybeHideBound, tru e);
189 delete this._bodyElement;
190 this._container.remove();
191 this._overlay.dispose();
192 delete this._overlay;
193 delete this._selectedElement;
194 this._selectedIndex = -1;
195 delete this._lastAnchorBox;
196 }
197
198 removeFromElement() {
199 this.hide();
200 }
201
202 /**
203 * @param {boolean=} isIntermediateSuggestion
204 * @return {boolean}
205 */
206 _applySuggestion(isIntermediateSuggestion) {
207 if (this._onlyCompletion) {
208 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermedi ateSuggestion);
209 return true;
210 }
211
212 if (!this.visible() || !this._selectedElement)
213 return false;
214
215 var suggestion = this._selectedElement.__fullValue;
216 if (!suggestion)
217 return false;
218
219 this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestio n);
220 return true;
221 }
222
223 /**
224 * @return {boolean}
225 */
226 acceptSuggestion() {
227 var result = this._applySuggestion();
228 this.hide();
229 if (!result)
230 return false;
231
232 this._suggestBoxDelegate.acceptSuggestion();
233
234 return true;
235 }
236
237 /**
238 * @param {number} shift
239 * @param {boolean=} isCircular
240 * @return {boolean} is changed
241 */
242 _selectClosest(shift, isCircular) {
243 if (!this._length)
244 return false;
245
246 this._userInteracted = true;
247
248 if (this._selectedIndex === -1 && shift < 0)
249 shift += 1;
250
251 var index = this._selectedIndex + shift;
252
253 if (isCircular)
254 index = (this._length + index) % this._length;
255 else
256 index = Number.constrain(index, 0, this._length - 1);
257
258 this._selectItem(index, true);
259 this._applySuggestion(true);
260 return true;
261 }
262
263 /**
264 * @param {!Event} event
265 */
266 _onItemMouseDown(event) {
267 this._selectedElement = event.currentTarget;
268 this.acceptSuggestion();
269 event.consume(true);
270 }
271
272 /**
273 * @param {string} prefix
274 * @param {string} text
275 * @param {string=} className
276 * @return {!Element}
277 */
278 _createItemElement(prefix, text, className) {
279 var element = createElementWithClass('div', 'suggest-box-content-item source -code ' + (className || ''));
280 element.tabIndex = -1;
281 if (prefix && prefix.length && !text.indexOf(prefix)) {
282 element.createChild('span', 'prefix').textContent = prefix;
283 element.createChild('span', 'suffix').textContent = text.substring(prefix. length).trimEnd(50);
284 } else {
285 element.createChild('span', 'suffix').textContent = text.trimEnd(50);
286 }
287 element.__fullValue = text;
288 element.createChild('span', 'spacer');
289 element.addEventListener('mousedown', this._onItemMouseDown.bind(this), fals e);
290 return element;
291 }
292
293 /**
294 * @param {!WebInspector.SuggestBox.Suggestions} items
295 * @param {string} userEnteredText
296 * @param {function(number): !Promise<{detail:string, description:string}>=} a syncDetails
297 */
298 _updateItems(items, userEnteredText, asyncDetails) {
299 this._length = items.length;
300 this._asyncDetailsPromises.clear();
301 this._asyncDetailsCallback = asyncDetails;
302 this._elementList = [];
303 delete this._selectedElement;
304
305 this._userEnteredText = userEnteredText;
306 this._items = items;
307 }
308
309 /**
310 * @param {number} index
311 * @return {!Promise<?{detail: string, description: string}>}
312 */
313 _asyncDetails(index) {
314 if (!this._asyncDetailsCallback)
315 return Promise.resolve(/** @type {?{description: string, detail: string}} */ (null));
316 if (!this._asyncDetailsPromises.has(index))
317 this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(index));
318 return /** @type {!Promise<?{detail: string, description: string}>} */ (this ._asyncDetailsPromises.get(index));
319 }
320
321 /**
322 * @param {?{detail: string, description: string}} details
323 */
324 _showDetailsPopup(details) {
325 this._detailsPopup.removeChildren();
326 if (!details)
327 return;
328 this._detailsPopup.createChild('section', 'detail').createTextChild(details. detail);
329 this._detailsPopup.createChild('section', 'description').createTextChild(det ails.description);
330 this._detailsPopup.classList.remove('hidden');
331 }
332
333 /**
334 * @param {number} index
335 * @param {boolean} scrollIntoView
336 */
337 _selectItem(index, scrollIntoView) {
338 if (this._selectedElement)
339 this._selectedElement.classList.remove('selected');
340
341 this._selectedIndex = index;
342 if (index < 0)
343 return;
344
345 this._selectedElement = this.itemElement(index);
346 this._selectedElement.classList.add('selected');
347 this._detailsPopup.classList.add('hidden');
348 var elem = this._selectedElement;
349 this._asyncDetails(index).then(showDetails.bind(this), function() {});
350
351 if (scrollIntoView)
352 this._viewport.scrollItemIntoView(index);
353
354 /**
355 * @param {?{detail: string, description: string}} details
356 * @this {WebInspector.SuggestBox}
357 */
358 function showDetails(details) {
359 if (elem === this._selectedElement)
360 this._showDetailsPopup(details);
361 }
362 }
363
364 /**
365 * @param {!WebInspector.SuggestBox.Suggestions} completions
366 * @param {boolean} canShowForSingleItem
367 * @param {string} userEnteredText
368 * @return {boolean}
369 */
370 _canShowBox(completions, canShowForSingleItem, userEnteredText) {
371 if (!completions || !completions.length)
372 return false;
373
374 if (completions.length > 1)
375 return true;
376
377 // Do not show a single suggestion if it is the same as user-entered prefix, even if allowed to show single-item suggest boxes.
378 return canShowForSingleItem && completions[0].title !== userEnteredText;
379 }
380
381 _ensureRowCountPerViewport() {
382 if (this._rowCountPerViewport)
383 return;
384 if (!this._items.length)
385 return;
386
387 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRect() .height / this._rowHeight);
388 }
389
390 /**
391 * @param {!AnchorBox} anchorBox
392 * @param {!WebInspector.SuggestBox.Suggestions} completions
393 * @param {number} selectedIndex
394 * @param {boolean} canShowForSingleItem
395 * @param {string} userEnteredText
396 * @param {function(number): !Promise<{detail:string, description:string}>=} a syncDetails
397 */
398 updateSuggestions(anchorBox, completions, selectedIndex, canShowForSingleItem, userEnteredText, asyncDetails) {
399 delete this._onlyCompletion;
400 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) {
401 this._updateItems(completions, userEnteredText, asyncDetails);
402 this._show();
403 this._updateBoxPosition(anchorBox);
404 this._updateWidth();
405 this._viewport.refresh();
406 this._selectItem(selectedIndex, selectedIndex > 0);
407 delete this._rowCountPerViewport;
408 } else {
409 if (completions.length === 1)
410 this._onlyCompletion = completions[0].title;
411 this.hide();
412 }
413 }
414
415 /**
416 * @param {!KeyboardEvent} event
417 * @return {boolean}
418 */
419 keyPressed(event) {
420 switch (event.key) {
421 case 'ArrowUp':
422 return this.upKeyPressed();
423 case 'ArrowDown':
424 return this.downKeyPressed();
425 case 'PageUp':
426 return this.pageUpKeyPressed();
427 case 'PageDown':
428 return this.pageDownKeyPressed();
429 case 'Enter':
430 return this.enterKeyPressed();
431 }
432 return false;
433 }
434
435 /**
436 * @return {boolean}
437 */
438 upKeyPressed() {
439 return this._selectClosest(-1, true);
440 }
441
442 /**
443 * @return {boolean}
444 */
445 downKeyPressed() {
446 return this._selectClosest(1, true);
447 }
448
449 /**
450 * @return {boolean}
451 */
452 pageUpKeyPressed() {
453 this._ensureRowCountPerViewport();
454 return this._selectClosest(-this._rowCountPerViewport, false);
455 }
456
457 /**
458 * @return {boolean}
459 */
460 pageDownKeyPressed() {
461 this._ensureRowCountPerViewport();
462 return this._selectClosest(this._rowCountPerViewport, false);
463 }
464
465 /**
466 * @return {boolean}
467 */
468 enterKeyPressed() {
469 if (!this._userInteracted && this._captureEnter)
470 return false;
471
472 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion;
473 this.acceptSuggestion();
474
475 // Report the event as non-handled if there is no selected item,
476 // to commit the input or handle it otherwise.
477 return hasSelectedItem;
478 }
479
480 /**
481 * @override
482 * @param {number} index
483 * @return {number}
484 */
485 fastItemHeight(index) {
486 return this._rowHeight;
487 }
488
489 /**
490 * @override
491 * @return {number}
492 */
493 itemCount() {
494 return this._items.length;
495 }
496
497 /**
498 * @override
499 * @param {number} index
500 * @return {?Element}
501 */
502 itemElement(index) {
503 if (!this._elementList[index])
504 this._elementList[index] =
505 this._createItemElement(this._userEnteredText, this._items[index].titl e, this._items[index].className);
506 return this._elementList[index];
507 }
87 }; 508 };
88 509
89 /** 510 /**
90 * @typedef {!Array.<{title: string, className: (string|undefined)}>} 511 * @typedef {!Array.<{title: string, className: (string|undefined)}>}
91 */ 512 */
92 WebInspector.SuggestBox.Suggestions; 513 WebInspector.SuggestBox.Suggestions;
93 514
94 WebInspector.SuggestBox.prototype = {
95 /**
96 * @return {boolean}
97 */
98 visible: function()
99 {
100 return !!this._container.parentElement;
101 },
102
103 /**
104 * @param {!AnchorBox} anchorBox
105 */
106 setPosition: function(anchorBox)
107 {
108 this._updateBoxPosition(anchorBox);
109 },
110
111 /**
112 * @param {!AnchorBox} anchorBox
113 */
114 _updateBoxPosition: function(anchorBox)
115 {
116 console.assert(this._overlay);
117 if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this ._lastItemCount === this.itemCount())
118 return;
119 this._lastItemCount = this.itemCount();
120 this._lastAnchorBox = anchorBox;
121
122 // Position relative to main DevTools element.
123 var container = WebInspector.Dialog.modalHostView().element;
124 anchorBox = anchorBox.relativeToElement(container);
125 var totalHeight = container.offsetHeight;
126 var aboveHeight = anchorBox.y;
127 var underHeight = totalHeight - anchorBox.y - anchorBox.height;
128
129 this._overlay.setLeftOffset(anchorBox.x);
130
131 var under = underHeight >= aboveHeight;
132 if (under)
133 this._overlay.setVerticalOffset(anchorBox.y + anchorBox.height, true );
134 else
135 this._overlay.setVerticalOffset(totalHeight - anchorBox.y, false);
136
137 var spacer = 6;
138 var maxHeight = Math.min(Math.max(underHeight, aboveHeight) - spacer, th is._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : 0);
139 var height = this._rowHeight * this._items.length;
140 this._hasVerticalScroll = height > maxHeight;
141 this._element.style.height = Math.min(maxHeight, height) + "px";
142 },
143
144 _updateWidth: function()
145 {
146 if (this._hasVerticalScroll) {
147 this._element.style.width = "100vw";
148 return;
149 }
150 // If there are no scrollbars, set the width to the width of the largest row.
151 var maxIndex = 0;
152 for (var i = 0; i < this._items.length; i++) {
153 if (this._items[i].title.length > this._items[maxIndex].title.length )
154 maxIndex = i;
155 }
156 var element = /** @type {!Element} */ (this.itemElement(maxIndex));
157 this._element.style.width = WebInspector.measurePreferredSize(element, t his._element).width + "px";
158 },
159
160 /**
161 * @param {!Event} event
162 */
163 _onBoxMouseDown: function(event)
164 {
165 if (this._hideTimeoutId) {
166 window.clearTimeout(this._hideTimeoutId);
167 delete this._hideTimeoutId;
168 }
169 event.preventDefault();
170 },
171
172 _maybeHide: function()
173 {
174 if (!this._hideTimeoutId)
175 this._hideTimeoutId = window.setTimeout(this.hide.bind(this), 0);
176 },
177
178 /**
179 * // FIXME: make SuggestBox work for multiple documents.
180 * @suppressGlobalPropertiesCheck
181 */
182 _show: function()
183 {
184 if (this.visible())
185 return;
186 this._bodyElement = document.body;
187 this._bodyElement.addEventListener("mousedown", this._maybeHideBound, tr ue);
188 this._overlay = new WebInspector.SuggestBox.Overlay();
189 this._overlay.setContentElement(this._container);
190 var measuringElement = this._createItemElement("1", "12");
191 this._viewport.element.appendChild(measuringElement);
192 this._rowHeight = measuringElement.getBoundingClientRect().height;
193 measuringElement.remove();
194 },
195
196 hide: function()
197 {
198 if (!this.visible())
199 return;
200
201 this._userInteracted = false;
202 this._bodyElement.removeEventListener("mousedown", this._maybeHideBound, true);
203 delete this._bodyElement;
204 this._container.remove();
205 this._overlay.dispose();
206 delete this._overlay;
207 delete this._selectedElement;
208 this._selectedIndex = -1;
209 delete this._lastAnchorBox;
210 },
211
212 removeFromElement: function()
213 {
214 this.hide();
215 },
216
217 /**
218 * @param {boolean=} isIntermediateSuggestion
219 * @return {boolean}
220 */
221 _applySuggestion: function(isIntermediateSuggestion)
222 {
223 if (this._onlyCompletion) {
224 this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isInt ermediateSuggestion);
225 return true;
226 }
227
228 if (!this.visible() || !this._selectedElement)
229 return false;
230
231 var suggestion = this._selectedElement.__fullValue;
232 if (!suggestion)
233 return false;
234
235 this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSugge stion);
236 return true;
237 },
238
239 /**
240 * @return {boolean}
241 */
242 acceptSuggestion: function()
243 {
244 var result = this._applySuggestion();
245 this.hide();
246 if (!result)
247 return false;
248
249 this._suggestBoxDelegate.acceptSuggestion();
250
251 return true;
252 },
253
254 /**
255 * @param {number} shift
256 * @param {boolean=} isCircular
257 * @return {boolean} is changed
258 */
259 _selectClosest: function(shift, isCircular)
260 {
261 if (!this._length)
262 return false;
263
264 this._userInteracted = true;
265
266 if (this._selectedIndex === -1 && shift < 0)
267 shift += 1;
268
269 var index = this._selectedIndex + shift;
270
271 if (isCircular)
272 index = (this._length + index) % this._length;
273 else
274 index = Number.constrain(index, 0, this._length - 1);
275
276 this._selectItem(index, true);
277 this._applySuggestion(true);
278 return true;
279 },
280
281 /**
282 * @param {!Event} event
283 */
284 _onItemMouseDown: function(event)
285 {
286 this._selectedElement = event.currentTarget;
287 this.acceptSuggestion();
288 event.consume(true);
289 },
290
291 /**
292 * @param {string} prefix
293 * @param {string} text
294 * @param {string=} className
295 * @return {!Element}
296 */
297 _createItemElement: function(prefix, text, className)
298 {
299 var element = createElementWithClass("div", "suggest-box-content-item so urce-code " + (className || ""));
300 element.tabIndex = -1;
301 if (prefix && prefix.length && !text.indexOf(prefix)) {
302 element.createChild("span", "prefix").textContent = prefix;
303 element.createChild("span", "suffix").textContent = text.substring(p refix.length).trimEnd(50);
304 } else {
305 element.createChild("span", "suffix").textContent = text.trimEnd(50) ;
306 }
307 element.__fullValue = text;
308 element.createChild("span", "spacer");
309 element.addEventListener("mousedown", this._onItemMouseDown.bind(this), false);
310 return element;
311 },
312
313 /**
314 * @param {!WebInspector.SuggestBox.Suggestions} items
315 * @param {string} userEnteredText
316 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
317 */
318 _updateItems: function(items, userEnteredText, asyncDetails)
319 {
320 this._length = items.length;
321 this._asyncDetailsPromises.clear();
322 this._asyncDetailsCallback = asyncDetails;
323 this._elementList = [];
324 delete this._selectedElement;
325
326 this._userEnteredText = userEnteredText;
327 this._items = items;
328 },
329
330 /**
331 * @param {number} index
332 * @return {!Promise<?{detail: string, description: string}>}
333 */
334 _asyncDetails: function(index)
335 {
336 if (!this._asyncDetailsCallback)
337 return Promise.resolve(/** @type {?{description: string, detail: str ing}} */(null));
338 if (!this._asyncDetailsPromises.has(index))
339 this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(ind ex));
340 return /** @type {!Promise<?{detail: string, description: string}>} */(t his._asyncDetailsPromises.get(index));
341 },
342
343 /**
344 * @param {?{detail: string, description: string}} details
345 */
346 _showDetailsPopup: function(details)
347 {
348 this._detailsPopup.removeChildren();
349 if (!details)
350 return;
351 this._detailsPopup.createChild("section", "detail").createTextChild(deta ils.detail);
352 this._detailsPopup.createChild("section", "description").createTextChild (details.description);
353 this._detailsPopup.classList.remove("hidden");
354 },
355
356 /**
357 * @param {number} index
358 * @param {boolean} scrollIntoView
359 */
360 _selectItem: function(index, scrollIntoView)
361 {
362 if (this._selectedElement)
363 this._selectedElement.classList.remove("selected");
364
365 this._selectedIndex = index;
366 if (index < 0)
367 return;
368
369 this._selectedElement = this.itemElement(index);
370 this._selectedElement.classList.add("selected");
371 this._detailsPopup.classList.add("hidden");
372 var elem = this._selectedElement;
373 this._asyncDetails(index).then(showDetails.bind(this), function(){});
374
375 if (scrollIntoView)
376 this._viewport.scrollItemIntoView(index);
377
378 /**
379 * @param {?{detail: string, description: string}} details
380 * @this {WebInspector.SuggestBox}
381 */
382 function showDetails(details)
383 {
384 if (elem === this._selectedElement)
385 this._showDetailsPopup(details);
386 }
387 },
388
389 /**
390 * @param {!WebInspector.SuggestBox.Suggestions} completions
391 * @param {boolean} canShowForSingleItem
392 * @param {string} userEnteredText
393 * @return {boolean}
394 */
395 _canShowBox: function(completions, canShowForSingleItem, userEnteredText)
396 {
397 if (!completions || !completions.length)
398 return false;
399
400 if (completions.length > 1)
401 return true;
402
403 // 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.
404 return canShowForSingleItem && completions[0].title !== userEnteredText;
405 },
406
407 _ensureRowCountPerViewport: function()
408 {
409 if (this._rowCountPerViewport)
410 return;
411 if (!this._items.length)
412 return;
413
414 this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRe ct().height / this._rowHeight);
415 },
416
417 /**
418 * @param {!AnchorBox} anchorBox
419 * @param {!WebInspector.SuggestBox.Suggestions} completions
420 * @param {number} selectedIndex
421 * @param {boolean} canShowForSingleItem
422 * @param {string} userEnteredText
423 * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
424 */
425 updateSuggestions: function(anchorBox, completions, selectedIndex, canShowFo rSingleItem, userEnteredText, asyncDetails)
426 {
427 delete this._onlyCompletion;
428 if (this._canShowBox(completions, canShowForSingleItem, userEnteredText) ) {
429 this._updateItems(completions, userEnteredText, asyncDetails);
430 this._show();
431 this._updateBoxPosition(anchorBox);
432 this._updateWidth();
433 this._viewport.refresh();
434 this._selectItem(selectedIndex, selectedIndex > 0);
435 delete this._rowCountPerViewport;
436 } else {
437 if (completions.length === 1)
438 this._onlyCompletion = completions[0].title;
439 this.hide();
440 }
441 },
442
443 /**
444 * @param {!KeyboardEvent} event
445 * @return {boolean}
446 */
447 keyPressed: function(event)
448 {
449 switch (event.key) {
450 case "ArrowUp":
451 return this.upKeyPressed();
452 case "ArrowDown":
453 return this.downKeyPressed();
454 case "PageUp":
455 return this.pageUpKeyPressed();
456 case "PageDown":
457 return this.pageDownKeyPressed();
458 case "Enter":
459 return this.enterKeyPressed();
460 }
461 return false;
462 },
463
464 /**
465 * @return {boolean}
466 */
467 upKeyPressed: function()
468 {
469 return this._selectClosest(-1, true);
470 },
471
472 /**
473 * @return {boolean}
474 */
475 downKeyPressed: function()
476 {
477 return this._selectClosest(1, true);
478 },
479
480 /**
481 * @return {boolean}
482 */
483 pageUpKeyPressed: function()
484 {
485 this._ensureRowCountPerViewport();
486 return this._selectClosest(-this._rowCountPerViewport, false);
487 },
488
489 /**
490 * @return {boolean}
491 */
492 pageDownKeyPressed: function()
493 {
494 this._ensureRowCountPerViewport();
495 return this._selectClosest(this._rowCountPerViewport, false);
496 },
497
498 /**
499 * @return {boolean}
500 */
501 enterKeyPressed: function()
502 {
503 if (!this._userInteracted && this._captureEnter)
504 return false;
505
506 var hasSelectedItem = !!this._selectedElement || this._onlyCompletion;
507 this.acceptSuggestion();
508
509 // Report the event as non-handled if there is no selected item,
510 // to commit the input or handle it otherwise.
511 return hasSelectedItem;
512 },
513
514 /**
515 * @override
516 * @param {number} index
517 * @return {number}
518 */
519 fastItemHeight: function(index)
520 {
521 return this._rowHeight;
522 },
523
524 /**
525 * @override
526 * @return {number}
527 */
528 itemCount: function()
529 {
530 return this._items.length;
531 },
532
533 /**
534 * @override
535 * @param {number} index
536 * @return {?Element}
537 */
538 itemElement: function(index)
539 {
540 if (!this._elementList[index])
541 this._elementList[index] = this._createItemElement(this._userEntered Text, this._items[index].title, this._items[index].className);
542 return this._elementList[index];
543 }
544 };
545
546 /** 515 /**
547 * @constructor 516 * @unrestricted
548 * // FIXME: make SuggestBox work for multiple documents.
549 * @suppressGlobalPropertiesCheck
550 */ 517 */
551 WebInspector.SuggestBox.Overlay = function() 518 WebInspector.SuggestBox.Overlay = class {
552 { 519 /**
553 this.element = createElementWithClass("div", "suggest-box-overlay"); 520 * // FIXME: make SuggestBox work for multiple documents.
554 var root = WebInspector.createShadowRootWithCoreStyles(this.element, "ui/sug gestBox.css"); 521 * @suppressGlobalPropertiesCheck
555 this._leftSpacerElement = root.createChild("div", "suggest-box-left-spacer") ; 522 */
556 this._horizontalElement = root.createChild("div", "suggest-box-horizontal"); 523 constructor() {
557 this._topSpacerElement = this._horizontalElement.createChild("div", "suggest -box-top-spacer"); 524 this.element = createElementWithClass('div', 'suggest-box-overlay');
558 this._bottomSpacerElement = this._horizontalElement.createChild("div", "sugg est-box-bottom-spacer"); 525 var root = WebInspector.createShadowRootWithCoreStyles(this.element, 'ui/sug gestBox.css');
526 this._leftSpacerElement = root.createChild('div', 'suggest-box-left-spacer') ;
527 this._horizontalElement = root.createChild('div', 'suggest-box-horizontal');
528 this._topSpacerElement = this._horizontalElement.createChild('div', 'suggest -box-top-spacer');
529 this._bottomSpacerElement = this._horizontalElement.createChild('div', 'sugg est-box-bottom-spacer');
559 this._resize(); 530 this._resize();
560 document.body.appendChild(this.element); 531 document.body.appendChild(this.element);
532 }
533
534 /**
535 * @param {number} offset
536 */
537 setLeftOffset(offset) {
538 this._leftSpacerElement.style.flexBasis = offset + 'px';
539 }
540
541 /**
542 * @param {number} offset
543 * @param {boolean} isTopOffset
544 */
545 setVerticalOffset(offset, isTopOffset) {
546 this.element.classList.toggle('under-anchor', isTopOffset);
547
548 if (isTopOffset) {
549 this._bottomSpacerElement.style.flexBasis = 'auto';
550 this._topSpacerElement.style.flexBasis = offset + 'px';
551 } else {
552 this._bottomSpacerElement.style.flexBasis = offset + 'px';
553 this._topSpacerElement.style.flexBasis = 'auto';
554 }
555 }
556
557 /**
558 * @param {!Element} element
559 */
560 setContentElement(element) {
561 this._horizontalElement.insertBefore(element, this._bottomSpacerElement);
562 }
563
564 _resize() {
565 var container = WebInspector.Dialog.modalHostView().element;
566 var containerBox = container.boxInWindow(container.ownerDocument.defaultView );
567
568 this.element.style.left = containerBox.x + 'px';
569 this.element.style.top = containerBox.y + 'px';
570 this.element.style.height = containerBox.height + 'px';
571 this.element.style.width = containerBox.width + 'px';
572 }
573
574 dispose() {
575 this.element.remove();
576 }
561 }; 577 };
562
563 WebInspector.SuggestBox.Overlay.prototype = {
564 /**
565 * @param {number} offset
566 */
567 setLeftOffset: function(offset)
568 {
569 this._leftSpacerElement.style.flexBasis = offset + "px";
570 },
571
572 /**
573 * @param {number} offset
574 * @param {boolean} isTopOffset
575 */
576 setVerticalOffset: function(offset, isTopOffset)
577 {
578 this.element.classList.toggle("under-anchor", isTopOffset);
579
580 if (isTopOffset) {
581 this._bottomSpacerElement.style.flexBasis = "auto";
582 this._topSpacerElement.style.flexBasis = offset + "px";
583 } else {
584 this._bottomSpacerElement.style.flexBasis = offset + "px";
585 this._topSpacerElement.style.flexBasis = "auto";
586 }
587 },
588
589 /**
590 * @param {!Element} element
591 */
592 setContentElement: function(element)
593 {
594 this._horizontalElement.insertBefore(element, this._bottomSpacerElement) ;
595 },
596
597 _resize: function()
598 {
599 var container = WebInspector.Dialog.modalHostView().element;
600 var containerBox = container.boxInWindow(container.ownerDocument.default View);
601
602 this.element.style.left = containerBox.x + "px";
603 this.element.style.top = containerBox.y + "px";
604 this.element.style.height = containerBox.height + "px";
605 this.element.style.width = containerBox.width + "px";
606 },
607
608 dispose: function()
609 {
610 this.element.remove();
611 }
612 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698