| Index: third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
|
| diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
|
| index 1b20de2e0162c3f1caf51a5295798dbd816e4dad..2492af997336363ffa703874f4cb32294c14892a 100644
|
| --- a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
|
| +++ b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
|
| @@ -46,8 +46,8 @@ UI.SuggestBoxDelegate.prototype = {
|
| };
|
|
|
| /**
|
| - * @implements {UI.ViewportControl.Provider}
|
| * @unrestricted
|
| + * @implements {UI.ListDelegate}
|
| */
|
| UI.SuggestBox = class {
|
| /**
|
| @@ -57,32 +57,26 @@ UI.SuggestBox = class {
|
| */
|
| constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) {
|
| this._suggestBoxDelegate = suggestBoxDelegate;
|
| - this._length = 0;
|
| - this._selectedIndex = -1;
|
| - this._selectedElement = null;
|
| this._maxItemsHeight = maxItemsHeight;
|
| this._maybeHideBound = this._maybeHide.bind(this);
|
| this._container = createElementWithClass('div', 'suggest-box-container');
|
| - this._viewport = new UI.ViewportControl(this);
|
| - this._element = this._viewport.element;
|
| + this._rowHeight = 17;
|
| + /** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */
|
| + this._list = new UI.ListControl(this);
|
| + this._element = this._list.element;
|
| this._element.classList.add('suggest-box');
|
| this._container.appendChild(this._element);
|
| this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true);
|
| this._detailsPopup = this._container.createChild('div', 'suggest-box details-popup monospace');
|
| this._detailsPopup.classList.add('hidden');
|
| this._asyncDetailsCallback = null;
|
| - /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */
|
| + /** @type {!Map<!UI.SuggestBox.Suggestion, !Promise<{detail: string, description: string}>>} */
|
| this._asyncDetailsPromises = new Map();
|
| this._userInteracted = false;
|
| this._captureEnter = captureEnter;
|
| - /** @type {!Array<!Element>} */
|
| - this._elementList = [];
|
| - this._rowHeight = 17;
|
| this._viewportWidth = '100vw';
|
| this._hasVerticalScroll = false;
|
| this._userEnteredText = '';
|
| - /** @type {!UI.SuggestBox.Suggestions} */
|
| - this._items = [];
|
| }
|
|
|
| /**
|
| @@ -96,17 +90,18 @@ UI.SuggestBox = class {
|
| * @param {!AnchorBox} anchorBox
|
| */
|
| setPosition(anchorBox) {
|
| - this._updateBoxPosition(anchorBox);
|
| + this._updateBoxPosition(anchorBox, this._list.length());
|
| }
|
|
|
| /**
|
| * @param {!AnchorBox} anchorBox
|
| + * @param {number} length
|
| */
|
| - _updateBoxPosition(anchorBox) {
|
| + _updateBoxPosition(anchorBox, length) {
|
| console.assert(this._overlay);
|
| - if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._lastItemCount === this.itemCount())
|
| + if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._lastItemCount === length)
|
| return;
|
| - this._lastItemCount = this.itemCount();
|
| + this._lastItemCount = length;
|
| this._lastAnchorBox = anchorBox;
|
|
|
| // Position relative to main DevTools element.
|
| @@ -128,24 +123,28 @@ UI.SuggestBox = class {
|
| var maxHeight = Math.min(
|
| Math.max(underHeight, aboveHeight) - spacer,
|
| this._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : Infinity);
|
| - var height = this._rowHeight * this._items.length;
|
| + var height = this._rowHeight * length;
|
| this._hasVerticalScroll = height > maxHeight;
|
| this._element.style.height = Math.min(maxHeight, height) + 'px';
|
| }
|
|
|
| - _updateWidth() {
|
| + /**
|
| + * @param {!UI.SuggestBox.Suggestions} items
|
| + */
|
| + _updateWidth(items) {
|
| if (this._hasVerticalScroll) {
|
| this._element.style.width = '100vw';
|
| return;
|
| }
|
| + if (!items.length)
|
| + return;
|
| // If there are no scrollbars, set the width to the width of the largest row.
|
| - var maxIndex = 0;
|
| - for (var i = 0; i < this._items.length; i++) {
|
| - if (this._items[i].title.length > this._items[maxIndex].title.length)
|
| - maxIndex = i;
|
| + var maxItem = items[0];
|
| + for (var i = 1; i < items.length; i++) {
|
| + if (items[i].title.length > maxItem.title.length)
|
| + maxItem = items[i];
|
| }
|
| - var element = /** @type {!Element} */ (this.itemElement(maxIndex));
|
| - this._element.style.width = UI.measurePreferredSize(element, this._element).width + 'px';
|
| + this._element.style.width = UI.measurePreferredSize(this.createElementForItem(maxItem), this._element).width + 'px';
|
| }
|
|
|
| /**
|
| @@ -175,10 +174,8 @@ UI.SuggestBox = class {
|
| this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true);
|
| this._overlay = new UI.SuggestBox.Overlay();
|
| this._overlay.setContentElement(this._container);
|
| - var measuringElement = this._createItemElement('1', '12');
|
| - this._viewport.element.appendChild(measuringElement);
|
| - this._rowHeight = measuringElement.getBoundingClientRect().height;
|
| - measuringElement.remove();
|
| + this._rowHeight =
|
| + UI.measurePreferredSize(this.createElementForItem({title: '1', subtitle: '12'}), this._element).height;
|
| }
|
|
|
| hide() {
|
| @@ -191,8 +188,6 @@ UI.SuggestBox = class {
|
| this._container.remove();
|
| this._overlay.dispose();
|
| delete this._overlay;
|
| - delete this._selectedElement;
|
| - this._selectedIndex = -1;
|
| delete this._lastAnchorBox;
|
| }
|
|
|
| @@ -210,10 +205,10 @@ UI.SuggestBox = class {
|
| return true;
|
| }
|
|
|
| - if (!this.visible() || !this._selectedElement)
|
| + if (!this.visible() || !this._list.selectedItem())
|
| return false;
|
|
|
| - var suggestion = this._selectedElement.__fullValue;
|
| + var suggestion = this._list.selectedItem().title;
|
| if (!suggestion)
|
| return false;
|
|
|
| @@ -236,57 +231,21 @@ UI.SuggestBox = class {
|
| }
|
|
|
| /**
|
| - * @param {number} shift
|
| - * @param {boolean=} isCircular
|
| - * @return {boolean} is changed
|
| - */
|
| - _selectClosest(shift, isCircular) {
|
| - if (!this._length)
|
| - return false;
|
| -
|
| - this._userInteracted = true;
|
| -
|
| - if (this._selectedIndex === -1 && shift < 0)
|
| - shift += 1;
|
| -
|
| - var index = this._selectedIndex + shift;
|
| -
|
| - if (isCircular)
|
| - index = (this._length + index) % this._length;
|
| - else
|
| - index = Number.constrain(index, 0, this._length - 1);
|
| -
|
| - this._selectItem(index);
|
| - return true;
|
| - }
|
| -
|
| - /**
|
| - * @param {!Event} event
|
| - */
|
| - _onItemMouseDown(event) {
|
| - this._selectedElement = event.currentTarget;
|
| - this.acceptSuggestion();
|
| - event.consume(true);
|
| - }
|
| -
|
| - /**
|
| - * @param {string} query
|
| - * @param {string} title
|
| - * @param {string=} subtitle
|
| - * @param {string=} iconType
|
| - * @param {boolean=} isSecondary
|
| + * @override
|
| + * @param {!UI.SuggestBox.Suggestion} item
|
| * @return {!Element}
|
| */
|
| - _createItemElement(query, title, subtitle, iconType, isSecondary) {
|
| + createElementForItem(item) {
|
| + var query = this._userEnteredText;
|
| var element = createElementWithClass('div', 'suggest-box-content-item source-code');
|
| - if (iconType) {
|
| - var icon = UI.Icon.create(iconType, 'suggestion-icon');
|
| + if (item.iconType) {
|
| + var icon = UI.Icon.create(item.iconType, 'suggestion-icon');
|
| element.appendChild(icon);
|
| }
|
| - if (isSecondary)
|
| + if (item.isSecondary)
|
| element.classList.add('secondary');
|
| element.tabIndex = -1;
|
| - var displayText = title.trimEnd(50 + query.length);
|
| + var displayText = item.title.trimEnd(50 + query.length);
|
|
|
| var titleElement = element.createChild('span', 'suggestion-title');
|
| var index = displayText.toLowerCase().indexOf(query.toLowerCase());
|
| @@ -296,41 +255,75 @@ UI.SuggestBox = class {
|
| titleElement.createChild('span', 'query').textContent = displayText.substring(index, index + query.length);
|
| titleElement.createChild('span').textContent = displayText.substring(index > -1 ? index + query.length : 0);
|
| titleElement.createChild('span', 'spacer');
|
| - if (subtitle) {
|
| + if (item.subtitle) {
|
| var subtitleElement = element.createChild('span', 'suggestion-subtitle');
|
| - subtitleElement.textContent = subtitle.trimEnd(15);
|
| + subtitleElement.textContent = item.subtitle.trimEnd(15);
|
| }
|
| - element.__fullValue = title;
|
| element.addEventListener('mousedown', this._onItemMouseDown.bind(this), false);
|
| return element;
|
| }
|
|
|
| /**
|
| - * @param {!UI.SuggestBox.Suggestions} items
|
| - * @param {string} userEnteredText
|
| - * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
|
| + * @override
|
| + * @param {!UI.SuggestBox.Suggestion} item
|
| + * @return {number}
|
| + */
|
| + heightForItem(item) {
|
| + return this._rowHeight;
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + * @param {!UI.SuggestBox.Suggestion} item
|
| + * @return {boolean}
|
| */
|
| - _updateItems(items, userEnteredText, asyncDetails) {
|
| - this._length = items.length;
|
| - this._asyncDetailsPromises.clear();
|
| - this._asyncDetailsCallback = asyncDetails;
|
| - this._elementList = [];
|
| - delete this._selectedElement;
|
| -
|
| - this._userEnteredText = userEnteredText;
|
| - this._items = items;
|
| + isItemSelectable(item) {
|
| + return true;
|
| }
|
|
|
| /**
|
| - * @param {number} index
|
| + * @override
|
| + * @param {?UI.SuggestBox.Suggestion} from
|
| + * @param {?UI.SuggestBox.Suggestion} to
|
| + * @param {?Element} fromElement
|
| + * @param {?Element} toElement
|
| + */
|
| + selectedItemChanged(from, to, fromElement, toElement) {
|
| + if (fromElement)
|
| + fromElement.classList.remove('selected', 'force-white-icons');
|
| + if (toElement)
|
| + toElement.classList.add('selected', 'force-white-icons');
|
| + if (!to)
|
| + return;
|
| + this._detailsPopup.classList.add('hidden');
|
| + this._asyncDetails(to).then(details => {
|
| + if (this._list.selectedItem() === to)
|
| + this._showDetailsPopup(details);
|
| + });
|
| + this._applySuggestion(true);
|
| + }
|
| +
|
| + /**
|
| + * @param {!Event} event
|
| + */
|
| + _onItemMouseDown(event) {
|
| + if (!this._list.onClick(event))
|
| + return;
|
| + this._userInteracted = true;
|
| + this.acceptSuggestion();
|
| + event.consume(true);
|
| + }
|
| +
|
| + /**
|
| + * @param {!UI.SuggestBox.Suggestion} item
|
| * @return {!Promise<?{detail: string, description: string}>}
|
| */
|
| - _asyncDetails(index) {
|
| + _asyncDetails(item) {
|
| if (!this._asyncDetailsCallback)
|
| return Promise.resolve(/** @type {?{description: string, detail: string}} */ (null));
|
| - if (!this._asyncDetailsPromises.has(index))
|
| - this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(index));
|
| - return /** @type {!Promise<?{detail: string, description: string}>} */ (this._asyncDetailsPromises.get(index));
|
| + if (!this._asyncDetailsPromises.has(item))
|
| + this._asyncDetailsPromises.set(item, this._asyncDetailsCallback(item));
|
| + return /** @type {!Promise<?{detail: string, description: string}>} */ (this._asyncDetailsPromises.get(item));
|
| }
|
|
|
| /**
|
| @@ -346,39 +339,6 @@ UI.SuggestBox = class {
|
| }
|
|
|
| /**
|
| - * @param {number} index
|
| - */
|
| - _selectItem(index) {
|
| - if (this._selectedElement) {
|
| - this._selectedElement.classList.remove('selected');
|
| - this._selectedElement.classList.remove('force-white-icons');
|
| - }
|
| -
|
| - this._selectedIndex = index;
|
| - if (index < 0)
|
| - return;
|
| -
|
| - this._selectedElement = this.itemElement(index);
|
| - this._selectedElement.classList.add('selected');
|
| - this._selectedElement.classList.add('force-white-icons');
|
| - this._detailsPopup.classList.add('hidden');
|
| - var elem = this._selectedElement;
|
| - this._asyncDetails(index).then(showDetails.bind(this), function() {});
|
| -
|
| - this._viewport.scrollItemIntoView(index);
|
| - this._applySuggestion(true);
|
| -
|
| - /**
|
| - * @param {?{detail: string, description: string}} details
|
| - * @this {UI.SuggestBox}
|
| - */
|
| - function showDetails(details) {
|
| - if (elem === this._selectedElement)
|
| - this._showDetailsPopup(details);
|
| - }
|
| - }
|
| -
|
| - /**
|
| * @param {!UI.SuggestBox.Suggestions} completions
|
| * @param {boolean} canShowForSingleItem
|
| * @param {string} userEnteredText
|
| @@ -398,15 +358,6 @@ UI.SuggestBox = class {
|
| return canShowForSingleItem && completions[0].title !== userEnteredText;
|
| }
|
|
|
| - _ensureRowCountPerViewport() {
|
| - if (this._rowCountPerViewport)
|
| - return;
|
| - if (!this._items.length)
|
| - return;
|
| -
|
| - this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRect().height / this._rowHeight);
|
| - }
|
| -
|
| /**
|
| * @param {!AnchorBox} anchorBox
|
| * @param {!UI.SuggestBox.Suggestions} completions
|
| @@ -424,11 +375,20 @@ UI.SuggestBox = class {
|
| asyncDetails) {
|
| delete this._onlyCompletion;
|
| if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) {
|
| - this._updateItems(completions, userEnteredText, asyncDetails);
|
| + this._asyncDetailsPromises.clear();
|
| + if (asyncDetails)
|
| + this._asyncDetailsCallback = item => asyncDetails(completions.indexOf(item));
|
| + else
|
| + this._asyncDetailsCallback = null;
|
| + this._userEnteredText = userEnteredText;
|
| +
|
| this._show();
|
| - this._updateBoxPosition(anchorBox);
|
| - this._updateWidth();
|
| - this._viewport.refresh();
|
| + this._updateBoxPosition(anchorBox, completions.length);
|
| + this._updateWidth(completions);
|
| +
|
| + this._list.setHeightMode(UI.ListHeightMode.Fixed);
|
| + this._list.replaceAllItems(completions);
|
| +
|
| var highestPriorityItem = -1;
|
| if (selectHighestPriority) {
|
| var highestPriority = -Infinity;
|
| @@ -440,8 +400,7 @@ UI.SuggestBox = class {
|
| }
|
| }
|
| }
|
| - this._selectItem(highestPriorityItem);
|
| - delete this._rowCountPerViewport;
|
| + this._list.selectItemAtIndex(highestPriorityItem, true);
|
| } else {
|
| if (completions.length === 1) {
|
| this._onlyCompletion = completions[0].title;
|
| @@ -456,100 +415,38 @@ UI.SuggestBox = class {
|
| * @return {boolean}
|
| */
|
| keyPressed(event) {
|
| - switch (event.key) {
|
| - case 'ArrowUp':
|
| - return this.upKeyPressed();
|
| - case 'ArrowDown':
|
| - return this.downKeyPressed();
|
| - case 'PageUp':
|
| - return this.pageUpKeyPressed();
|
| - case 'PageDown':
|
| - return this.pageDownKeyPressed();
|
| - case 'Enter':
|
| - return this.enterKeyPressed();
|
| + if (this._list.onKeyDown(event)) {
|
| + this._userInteracted = true;
|
| + return true;
|
| }
|
| + if (event.key === 'Enter')
|
| + return this.enterKeyPressed();
|
| return false;
|
| }
|
|
|
| /**
|
| * @return {boolean}
|
| */
|
| - upKeyPressed() {
|
| - return this._selectClosest(-1, true);
|
| - }
|
| -
|
| - /**
|
| - * @return {boolean}
|
| - */
|
| - downKeyPressed() {
|
| - return this._selectClosest(1, true);
|
| - }
|
| -
|
| - /**
|
| - * @return {boolean}
|
| - */
|
| - pageUpKeyPressed() {
|
| - this._ensureRowCountPerViewport();
|
| - return this._selectClosest(-this._rowCountPerViewport, false);
|
| - }
|
| -
|
| - /**
|
| - * @return {boolean}
|
| - */
|
| - pageDownKeyPressed() {
|
| - this._ensureRowCountPerViewport();
|
| - return this._selectClosest(this._rowCountPerViewport, false);
|
| - }
|
| -
|
| - /**
|
| - * @return {boolean}
|
| - */
|
| enterKeyPressed() {
|
| if (!this._userInteracted && this._captureEnter)
|
| return false;
|
|
|
| - var hasSelectedItem = !!this._selectedElement || this._onlyCompletion;
|
| + var hasSelectedItem = !!this._list.selectedItem() || this._onlyCompletion;
|
| this.acceptSuggestion();
|
|
|
| // Report the event as non-handled if there is no selected item,
|
| // to commit the input or handle it otherwise.
|
| return hasSelectedItem;
|
| }
|
| -
|
| - /**
|
| - * @override
|
| - * @param {number} index
|
| - * @return {number}
|
| - */
|
| - fastItemHeight(index) {
|
| - return this._rowHeight;
|
| - }
|
| -
|
| - /**
|
| - * @override
|
| - * @return {number}
|
| - */
|
| - itemCount() {
|
| - return this._items.length;
|
| - }
|
| -
|
| - /**
|
| - * @override
|
| - * @param {number} index
|
| - * @return {?Element}
|
| - */
|
| - itemElement(index) {
|
| - if (!this._elementList[index]) {
|
| - this._elementList[index] = this._createItemElement(
|
| - this._userEnteredText, this._items[index].title, this._items[index].subtitle, this._items[index].iconType,
|
| - this._items[index].isSecondary);
|
| - }
|
| - return this._elementList[index];
|
| - }
|
| };
|
|
|
| /**
|
| - * @typedef {!Array.<{title: string, subtitle: (string|undefined), iconType: (string|undefined), priority: (number|undefined), isSecondary: (boolean|undefined)}>}
|
| + * @typedef {!{title: string, subtitle: (string|undefined), iconType: (string|undefined), priority: (number|undefined), isSecondary: (boolean|undefined)}}
|
| + */
|
| +UI.SuggestBox.Suggestion;
|
| +
|
| +/**
|
| + * @typedef {!Array<!UI.SuggestBox.Suggestion>}
|
| */
|
| UI.SuggestBox.Suggestions;
|
|
|
|
|