OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
5 */ | 5 */ |
6 /** | 6 /** |
7 * @unrestricted | 7 * @unrestricted |
8 * @implements {UI.ListDelegate} | 8 * @implements {UI.ListDelegate} |
9 */ | 9 */ |
10 QuickOpen.FilteredListWidget = class extends UI.VBox { | 10 QuickOpen.FilteredListWidget = class extends UI.VBox { |
11 /** | 11 /** |
12 * @param {?QuickOpen.FilteredListWidget.Provider} provider | 12 * @param {?QuickOpen.FilteredListWidget.Provider} provider |
13 * @param {!Array<string>=} promptHistory | 13 * @param {!Array<string>=} promptHistory |
14 * @param {function(string)=} queryChangedCallback | 14 * @param {function(string)=} queryChangedCallback |
15 */ | 15 */ |
16 constructor(provider, promptHistory, queryChangedCallback) { | 16 constructor(provider, promptHistory, queryChangedCallback) { |
17 super(true); | 17 super(true); |
18 this._promptHistory = promptHistory || []; | 18 this._promptHistory = promptHistory || []; |
19 | 19 |
20 this.contentElement.classList.add('filtered-list-widget'); | 20 this.contentElement.classList.add('filtered-list-widget'); |
21 this.contentElement.addEventListener('keydown', this._onKeyDown.bind(this), true); | 21 this.contentElement.addEventListener('keydown', this._onKeyDown.bind(this), true); |
22 this.registerRequiredCSS('quick_open/filteredListWidget.css'); | 22 this.registerRequiredCSS('quick_open/filteredListWidget.css'); |
23 | 23 |
24 this._promptElement = this.contentElement.createChild('div', 'filtered-list- widget-input'); | 24 var promptContainer = this.contentElement.createChild('div', 'filtered-list- widget-input-container'); |
25 this._iconContainer = promptContainer.createChild('div'); | |
26 var container = promptContainer.createChild('div', 'filtered-list-widget-inp ut'); | |
27 this._promptElement = container.createChild('div'); | |
25 this._promptElement.setAttribute('spellcheck', 'false'); | 28 this._promptElement.setAttribute('spellcheck', 'false'); |
26 this._promptElement.setAttribute('contenteditable', 'plaintext-only'); | 29 this._promptElement.setAttribute('contenteditable', 'plaintext-only'); |
27 this._prompt = new UI.TextPrompt(); | 30 this._prompt = new UI.TextPrompt(); |
28 this._prompt.initialize(() => Promise.resolve([])); | 31 this._prompt.initialize(() => Promise.resolve([])); |
29 this._prompt.renderAsBlock(); | 32 this._prompt.renderAsBlock(); |
30 var promptProxy = this._prompt.attach(this._promptElement); | 33 var promptProxy = this._prompt.attach(this._promptElement); |
31 promptProxy.addEventListener('input', this._onInput.bind(this), false); | 34 promptProxy.addEventListener('input', this._onInput.bind(this), false); |
32 promptProxy.classList.add('filtered-list-widget-prompt-element'); | 35 promptProxy.classList.add('filtered-list-widget-prompt-element'); |
33 | 36 |
34 this._bottomElementsContainer = this.contentElement.createChild('div', 'vbox '); | 37 this._bottomElementsContainer = this.contentElement.createChild('div', 'vbox '); |
35 this._progressElement = this._bottomElementsContainer.createChild('div', 'fi ltered-list-widget-progress'); | 38 this._progressElement = this._bottomElementsContainer.createChild('div', 'fi ltered-list-widget-progress'); |
36 this._progressBarElement = this._progressElement.createChild('div', 'filtere d-list-widget-progress-bar'); | 39 this._progressBarElement = this._progressElement.createChild('div', 'filtere d-list-widget-progress-bar'); |
37 | 40 |
38 /** @type {!UI.ListControl<number>} */ | 41 /** @type {!UI.ListControl<number>} */ |
39 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems); | 42 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems); |
40 this._itemElementsContainer = this._list.element; | 43 this._itemElementsContainer = this._list.element; |
44 this._itemElementsContainer.addEventListener('mousemove', event => this._upd ateHover(event)); | |
45 this._itemElementsContainer.addEventListener('mouseout', event => this._upda teHover(event)); | |
41 this._itemElementsContainer.classList.add('container'); | 46 this._itemElementsContainer.classList.add('container'); |
42 this._bottomElementsContainer.appendChild(this._itemElementsContainer); | 47 this._bottomElementsContainer.appendChild(this._itemElementsContainer); |
43 this._itemElementsContainer.addEventListener('click', this._onClick.bind(thi s), false); | 48 this._itemElementsContainer.addEventListener('click', this._onClick.bind(thi s), false); |
44 | 49 |
45 this._notFoundElement = this._bottomElementsContainer.createChild('div', 'no t-found-text'); | 50 this._notFoundElement = this._bottomElementsContainer.createChild('div', 'no t-found-text'); |
46 this._notFoundElement.classList.add('hidden'); | 51 this._notFoundElement.classList.add('hidden'); |
47 | 52 |
48 this.setDefaultFocusedElement(this._promptElement); | 53 this.setDefaultFocusedElement(this._promptElement); |
49 | 54 |
50 this._prefix = ''; | 55 this._prefix = ''; |
51 this._provider = provider; | 56 this._provider = provider; |
52 this._queryChangedCallback = queryChangedCallback; | 57 this._queryChangedCallback = queryChangedCallback; |
58 | |
59 /** @type {?number} */ | |
60 this._selectedItemIndex = null; | |
53 } | 61 } |
54 | 62 |
55 /** | 63 /** |
56 * @param {string} query | 64 * @param {string} query |
57 * @return {!RegExp} | 65 * @return {!RegExp} |
58 */ | 66 */ |
59 static filterRegex(query) { | 67 static filterRegex(query) { |
60 const toEscape = String.regexSpecialCharacters(); | 68 const toEscape = String.regexSpecialCharacters(); |
61 var regexString = ''; | 69 var regexString = ''; |
62 for (var i = 0; i < query.length; ++i) { | 70 for (var i = 0; i < query.length; ++i) { |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
104 var ranges = rangesForMatch(text, query); | 112 var ranges = rangesForMatch(text, query); |
105 if (!ranges || caseInsensitive) | 113 if (!ranges || caseInsensitive) |
106 ranges = rangesForMatch(text.toUpperCase(), query.toUpperCase()); | 114 ranges = rangesForMatch(text.toUpperCase(), query.toUpperCase()); |
107 if (ranges) { | 115 if (ranges) { |
108 UI.highlightRangesWithStyleClass(element, ranges, 'highlight'); | 116 UI.highlightRangesWithStyleClass(element, ranges, 'highlight'); |
109 return true; | 117 return true; |
110 } | 118 } |
111 return false; | 119 return false; |
112 } | 120 } |
113 | 121 |
114 showAsDialog() { | 122 /** |
123 * @param {!AnchorBox=} anchorBox | |
124 * @param {!UI.GlassPane.AnchorBehavior=} anchorBehavior | |
125 */ | |
126 showAsDialog(anchorBox, anchorBehavior) { | |
115 this._dialog = new UI.Dialog(); | 127 this._dialog = new UI.Dialog(); |
128 if (anchorBox) | |
129 this._dialog.setContentAnchorBox(anchorBox); | |
130 else | |
131 this._dialog.setContentPosition(null, 22); | |
116 this._dialog.setMaxContentSize(new UI.Size(504, 340)); | 132 this._dialog.setMaxContentSize(new UI.Size(504, 340)); |
117 this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHeigh t); | 133 this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHeigh t); |
118 this._dialog.setContentPosition(null, 22); | 134 if (anchorBehavior) |
135 this._dialog.setAnchorBehavior(anchorBehavior); | |
119 this.show(this._dialog.contentElement); | 136 this.show(this._dialog.contentElement); |
120 this._dialog.show(); | 137 this._dialog.show(); |
121 } | 138 } |
122 | 139 |
123 /** | 140 /** |
124 * @param {string} prefix | 141 * @param {string} prefix |
125 */ | 142 */ |
126 setPrefix(prefix) { | 143 setPrefix(prefix) { |
127 this._prefix = prefix; | 144 this._prefix = prefix; |
128 } | 145 } |
129 | 146 |
130 /** | 147 /** |
131 * @param {?QuickOpen.FilteredListWidget.Provider} provider | 148 * @param {?QuickOpen.FilteredListWidget.Provider} provider |
132 */ | 149 */ |
133 setProvider(provider) { | 150 setProvider(provider) { |
134 if (provider === this._provider) | 151 if (provider === this._provider) |
135 return; | 152 return; |
136 | 153 |
137 if (this._provider) | 154 if (this._provider) |
138 this._provider.detach(); | 155 this._provider.detach(); |
139 this._clearTimers(); | 156 this._clearTimers(); |
140 | 157 |
141 this._provider = provider; | 158 this._provider = provider; |
142 if (this.isShowing()) | 159 if (this.isShowing()) |
143 this._attachProvider(); | 160 this._attachProvider(); |
144 } | 161 } |
145 | 162 |
163 /** | |
164 * @param {?string} icon | |
165 */ | |
166 setInputIcon(icon) { | |
167 this._iconContainer.removeChildren(); | |
168 if (icon) | |
169 this._iconContainer.appendChild(UI.Icon.create(icon, 'filtered-list-input- icon')); | |
170 } | |
171 | |
146 _attachProvider() { | 172 _attachProvider() { |
147 if (this._provider) { | 173 if (this._provider) { |
148 this._provider.setRefreshCallback(this._itemsLoaded.bind(this, this._provi der)); | 174 this._provider.setRefreshCallback(this._itemsLoaded.bind(this, this._provi der)); |
149 this._provider.attach(); | 175 this._provider.attach(); |
150 } | 176 } |
151 this._itemsLoaded(this._provider); | 177 this._itemsLoaded(this._provider); |
152 } | 178 } |
153 | 179 |
154 /** | 180 /** |
155 * @return {string} | 181 * @return {string} |
156 */ | 182 */ |
157 _value() { | 183 _value() { |
158 return this._prompt.text().trim(); | 184 return this._prompt.text().trim(); |
159 } | 185 } |
160 | 186 |
161 _cleanValue() { | 187 _cleanValue() { |
162 return this._value().substring(this._prefix.length); | 188 return this._value().substring(this._prefix.length); |
163 } | 189 } |
164 | 190 |
165 /** | 191 /** |
192 * @param {!Event} event | |
193 */ | |
194 _updateHover(event) { | |
195 var element = event.deepElementFromPoint(); | |
dgozman
2017/04/05 20:52:55
Implement this similarly to click?
var item = thi
eostroukhov
2017/05/03 00:33:46
Done.
| |
196 var listItemElement = element && element.enclosingNodeOrSelfWithClass('filte red-list-widget-item'); | |
197 if (listItemElement && listItemElement.classList.contains('hovered')) | |
198 return; | |
199 for (var child of this._list.element.getElementsByClassName('hovered')) | |
dgozman
2017/04/05 20:52:55
Introduce |this._hoverElement| and remove the clas
eostroukhov
2017/05/03 00:33:46
Done.
| |
200 child.classList.remove('hovered'); | |
201 var item = listItemElement && this._list.itemForNode(listItemElement); | |
202 if (listItemElement) | |
203 listItemElement.classList.add('hovered'); | |
dgozman
2017/04/05 20:52:56
This should go to _hover.
eostroukhov
2017/05/03 00:33:46
Done.
| |
204 this._hover(item); | |
205 } | |
206 | |
207 /** | |
166 * @override | 208 * @override |
167 */ | 209 */ |
168 wasShown() { | 210 wasShown() { |
169 this._list.invalidateItemHeight(); | 211 this._list.invalidateItemHeight(); |
170 this._attachProvider(); | 212 this._attachProvider(); |
171 } | 213 } |
172 | 214 |
173 /** | 215 /** |
174 * @override | 216 * @override |
175 */ | 217 */ |
176 willHide() { | 218 willHide() { |
219 this._hover(null); | |
177 if (this._provider) | 220 if (this._provider) |
178 this._provider.detach(); | 221 this._provider.detach(); |
179 this._clearTimers(); | 222 this._clearTimers(); |
180 } | 223 } |
181 | 224 |
182 _clearTimers() { | 225 _clearTimers() { |
183 clearTimeout(this._filterTimer); | 226 clearTimeout(this._filterTimer); |
184 clearTimeout(this._scoringTimer); | 227 clearTimeout(this._scoringTimer); |
185 clearTimeout(this._loadTimeout); | 228 clearTimeout(this._loadTimeout); |
186 delete this._filterTimer; | 229 delete this._filterTimer; |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
257 * @param {?number} from | 300 * @param {?number} from |
258 * @param {?number} to | 301 * @param {?number} to |
259 * @param {?Element} fromElement | 302 * @param {?Element} fromElement |
260 * @param {?Element} toElement | 303 * @param {?Element} toElement |
261 */ | 304 */ |
262 selectedItemChanged(from, to, fromElement, toElement) { | 305 selectedItemChanged(from, to, fromElement, toElement) { |
263 if (fromElement) | 306 if (fromElement) |
264 fromElement.classList.remove('selected'); | 307 fromElement.classList.remove('selected'); |
265 if (toElement) | 308 if (toElement) |
266 toElement.classList.add('selected'); | 309 toElement.classList.add('selected'); |
310 this._hover(to); | |
dgozman
2017/04/05 20:52:55
Why this?
eostroukhov
2017/05/03 00:33:46
For keyboard navigation. It is extremely confusing
| |
267 } | 311 } |
268 | 312 |
269 /** | 313 /** |
314 * @param {number} item | |
315 */ | |
316 selectItem(item) { | |
dgozman
2017/04/05 20:52:55
setInitialSelection
eostroukhov
2017/05/03 00:33:46
Done.
| |
317 if (this._list.length() > 0) | |
318 this._list.selectItem(item, true); | |
319 else | |
320 this._selectedItemIndex = item; | |
dgozman
2017/04/05 20:52:55
_initialSelectedItem
eostroukhov
2017/05/03 00:33:46
Done.
| |
321 } | |
322 | |
323 /** | |
270 * @param {!Event} event | 324 * @param {!Event} event |
271 */ | 325 */ |
272 _onClick(event) { | 326 _onClick(event) { |
273 var item = this._list.itemForNode(/** @type {?Node} */ (event.target)); | 327 var item = this._list.itemForNode(/** @type {?Node} */ (event.target)); |
274 if (item === null) | 328 if (item === null) |
275 return; | 329 return; |
276 | 330 |
277 event.consume(true); | 331 event.consume(true); |
278 // Detach dialog before allowing provider to override focus. | 332 // Detach dialog before allowing provider to override focus. |
279 if (this._dialog) | 333 if (this._dialog) |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
418 * @param {!Array<number>} bestItems | 472 * @param {!Array<number>} bestItems |
419 * @param {!Array<number>} overflowItems | 473 * @param {!Array<number>} overflowItems |
420 * @param {!Array<number>} filteredItems | 474 * @param {!Array<number>} filteredItems |
421 */ | 475 */ |
422 _refreshList(bestItems, overflowItems, filteredItems) { | 476 _refreshList(bestItems, overflowItems, filteredItems) { |
423 delete this._refreshListWithCurrentResult; | 477 delete this._refreshListWithCurrentResult; |
424 filteredItems = [].concat(bestItems, overflowItems, filteredItems); | 478 filteredItems = [].concat(bestItems, overflowItems, filteredItems); |
425 this._updateNotFoundMessage(!!filteredItems.length); | 479 this._updateNotFoundMessage(!!filteredItems.length); |
426 var oldHeight = this._list.element.offsetHeight; | 480 var oldHeight = this._list.element.offsetHeight; |
427 this._list.replaceAllItems(filteredItems); | 481 this._list.replaceAllItems(filteredItems); |
428 if (filteredItems.length) | 482 if (filteredItems.length) { |
429 this._list.selectItem(filteredItems[0]); | 483 var selection = filteredItems[0]; |
484 if (this._selectedItemIndex !== null && filteredItems.includes(this._selec tedItemIndex)) | |
485 selection = this._selectedItemIndex; | |
486 this._selectedItemIndex = null; | |
487 this._list.selectItem(selection, true); | |
488 } | |
430 if (this._list.element.offsetHeight !== oldHeight) | 489 if (this._list.element.offsetHeight !== oldHeight) |
431 this._list.viewportResized(); | 490 this._list.viewportResized(); |
432 this._itemsFilteredForTest(); | 491 this._itemsFilteredForTest(); |
433 } | 492 } |
434 | 493 |
435 /** | 494 /** |
436 * @param {boolean} hasItems | 495 * @param {boolean} hasItems |
437 */ | 496 */ |
438 _updateNotFoundMessage(hasItems) { | 497 _updateNotFoundMessage(hasItems) { |
439 this._list.element.classList.toggle('hidden', !hasItems); | 498 this._list.element.classList.toggle('hidden', !hasItems); |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
491 | 550 |
492 /** | 551 /** |
493 * @param {?number} itemIndex | 552 * @param {?number} itemIndex |
494 */ | 553 */ |
495 _selectItem(itemIndex) { | 554 _selectItem(itemIndex) { |
496 this._promptHistory.push(this._value()); | 555 this._promptHistory.push(this._value()); |
497 if (this._promptHistory.length > 100) | 556 if (this._promptHistory.length > 100) |
498 this._promptHistory.shift(); | 557 this._promptHistory.shift(); |
499 this._provider.selectItem(itemIndex, this._cleanValue()); | 558 this._provider.selectItem(itemIndex, this._cleanValue()); |
500 } | 559 } |
560 | |
561 /** | |
562 * @param {?number} item | |
563 */ | |
564 _hover(item) { | |
565 if (this._provider) | |
566 this._provider.hoverItem(item); | |
567 } | |
501 }; | 568 }; |
502 | 569 |
503 | 570 |
504 /** | 571 /** |
505 * @unrestricted | 572 * @unrestricted |
506 */ | 573 */ |
507 QuickOpen.FilteredListWidget.Provider = class { | 574 QuickOpen.FilteredListWidget.Provider = class { |
508 /** | 575 /** |
509 * @param {function():void} refreshCallback | 576 * @param {function():void} refreshCallback |
510 */ | 577 */ |
(...skipping 22 matching lines...) Expand all Loading... | |
533 /** | 600 /** |
534 * @param {number} itemIndex | 601 * @param {number} itemIndex |
535 * @param {string} query | 602 * @param {string} query |
536 * @return {number} | 603 * @return {number} |
537 */ | 604 */ |
538 itemScoreAt(itemIndex, query) { | 605 itemScoreAt(itemIndex, query) { |
539 return 1; | 606 return 1; |
540 } | 607 } |
541 | 608 |
542 /** | 609 /** |
610 * @param {?number} itemIndex | |
611 */ | |
612 hoverItem(itemIndex) { | |
613 } | |
614 | |
615 /** | |
543 * @param {number} itemIndex | 616 * @param {number} itemIndex |
544 * @param {string} query | 617 * @param {string} query |
545 * @param {!Element} titleElement | 618 * @param {!Element} titleElement |
546 * @param {!Element} subtitleElement | 619 * @param {!Element} subtitleElement |
547 */ | 620 */ |
548 renderItem(itemIndex, query, titleElement, subtitleElement) { | 621 renderItem(itemIndex, query, titleElement, subtitleElement) { |
549 } | 622 } |
550 | 623 |
551 /** | 624 /** |
552 * @return {boolean} | 625 * @return {boolean} |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
584 * @param {string} query | 657 * @param {string} query |
585 * @return {string} | 658 * @return {string} |
586 */ | 659 */ |
587 notFoundText(query) { | 660 notFoundText(query) { |
588 return Common.UIString('No results found'); | 661 return Common.UIString('No results found'); |
589 } | 662 } |
590 | 663 |
591 detach() { | 664 detach() { |
592 } | 665 } |
593 }; | 666 }; |
OLD | NEW |