OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 }; |
OLD | NEW |