mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-11 14:14:36 +08:00
Move and rename files. NOAUTOREVERT=true NOPRESUBMIT=true NOTREECHECKS=true Bug: 768828 TBR=darin@chromium.org NOTRY=true Change-Id: I66d3b155808bc5bdbf237b80208e1e552bcf7f28 Reviewed-on: https://chromium-review.googlesource.com/1001153 Reviewed-by: Blink Reformat <blink-reformat@chromium.org> Commit-Queue: Blink Reformat <blink-reformat@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#549061} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 0aee4434a4dba42a42abaea9bfbc0cd196a63bc1
397 lines
12 KiB
JavaScript
397 lines
12 KiB
JavaScript
/*
|
|
* Copyright (C) 2013 Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/**
|
|
* @interface
|
|
*/
|
|
UI.SuggestBoxDelegate = function() {};
|
|
|
|
UI.SuggestBoxDelegate.prototype = {
|
|
/**
|
|
* @param {string} suggestion
|
|
* @param {boolean=} isIntermediateSuggestion
|
|
*/
|
|
applySuggestion(suggestion, isIntermediateSuggestion) {},
|
|
|
|
/**
|
|
* acceptSuggestion will be always called after call to applySuggestion with isIntermediateSuggestion being equal to false.
|
|
*/
|
|
acceptSuggestion() {},
|
|
};
|
|
|
|
/**
|
|
* @implements {UI.ListDelegate}
|
|
*/
|
|
UI.SuggestBox = class {
|
|
/**
|
|
* @param {!UI.SuggestBoxDelegate} suggestBoxDelegate
|
|
* @param {number=} maxItemsHeight
|
|
* @param {boolean=} captureEnter
|
|
*/
|
|
constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) {
|
|
this._suggestBoxDelegate = suggestBoxDelegate;
|
|
this._maxItemsHeight = maxItemsHeight;
|
|
this._captureEnter = captureEnter;
|
|
this._rowHeight = 17;
|
|
this._userInteracted = false;
|
|
this._userEnteredText = '';
|
|
this._defaultSelectionIsDimmed = false;
|
|
|
|
/** @type {?string} */
|
|
this._onlyCompletion = null;
|
|
|
|
/** @type {!UI.ListModel<!UI.SuggestBox.Suggestion>} */
|
|
this._items = new UI.ListModel();
|
|
/** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */
|
|
this._list = new UI.ListControl(this._items, this, UI.ListMode.EqualHeightItems);
|
|
this._element = this._list.element;
|
|
this._element.classList.add('suggest-box');
|
|
this._element.addEventListener('mousedown', event => event.preventDefault(), true);
|
|
this._element.addEventListener('click', this._onClick.bind(this), false);
|
|
|
|
this._glassPane = new UI.GlassPane();
|
|
this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom);
|
|
this._glassPane.setOutsideClickCallback(this.hide.bind(this));
|
|
const shadowRoot = UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'ui/suggestBox.css');
|
|
shadowRoot.appendChild(this._element);
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} value
|
|
*/
|
|
setDefaultSelectionIsDimmed(value) {
|
|
this._defaultSelectionIsDimmed = value;
|
|
this._element.classList.toggle('default-selection-is-dimmed', value);
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} value
|
|
*/
|
|
_setUserInteracted(value) {
|
|
this._userInteracted = value;
|
|
this._element.classList.toggle('user-has-interacted', value);
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
visible() {
|
|
return this._glassPane.isShowing();
|
|
}
|
|
|
|
/**
|
|
* @param {!AnchorBox} anchorBox
|
|
*/
|
|
setPosition(anchorBox) {
|
|
this._glassPane.setContentAnchorBox(anchorBox);
|
|
}
|
|
|
|
/**
|
|
* @param {!UI.SuggestBox.Suggestions} items
|
|
*/
|
|
_updateMaxSize(items) {
|
|
const maxWidth = this._maxWidth(items);
|
|
const length = this._maxItemsHeight ? Math.min(this._maxItemsHeight, items.length) : items.length;
|
|
const maxHeight = length * this._rowHeight;
|
|
this._glassPane.setMaxContentSize(new UI.Size(maxWidth, maxHeight));
|
|
}
|
|
|
|
/**
|
|
* @param {!UI.SuggestBox.Suggestions} items
|
|
* @return {number}
|
|
*/
|
|
_maxWidth(items) {
|
|
const kMaxWidth = 300;
|
|
if (!items.length)
|
|
return kMaxWidth;
|
|
let maxItem;
|
|
let maxLength = -Infinity;
|
|
for (let i = 0; i < items.length; i++) {
|
|
const length = (items[i].title || items[i].text).length + (items[i].subtitle || '').length;
|
|
if (length > maxLength) {
|
|
maxLength = length;
|
|
maxItem = items[i];
|
|
}
|
|
}
|
|
const element = this.createElementForItem(/** @type {!UI.SuggestBox.Suggestion} */ (maxItem));
|
|
return Math.min(kMaxWidth, UI.measurePreferredSize(element, this._element).width);
|
|
}
|
|
|
|
/**
|
|
* @suppressGlobalPropertiesCheck
|
|
*/
|
|
_show() {
|
|
if (this.visible())
|
|
return;
|
|
// TODO(dgozman): take document as a parameter.
|
|
this._glassPane.show(document);
|
|
this._rowHeight =
|
|
UI.measurePreferredSize(this.createElementForItem({text: '1', subtitle: '12'}), this._element).height;
|
|
}
|
|
|
|
hide() {
|
|
if (!this.visible())
|
|
return;
|
|
this._setUserInteracted(false);
|
|
this._glassPane.hide();
|
|
}
|
|
|
|
/**
|
|
* @param {boolean=} isIntermediateSuggestion
|
|
* @return {boolean}
|
|
*/
|
|
_applySuggestion(isIntermediateSuggestion) {
|
|
if (this._onlyCompletion) {
|
|
UI.ARIAUtils.alert(ls`${this._onlyCompletion}, suggestion`, this._element);
|
|
this._suggestBoxDelegate.applySuggestion(this._onlyCompletion, isIntermediateSuggestion);
|
|
return true;
|
|
}
|
|
const suggestion = this._list.selectedItem() ? this._list.selectedItem().text : '';
|
|
if (suggestion)
|
|
UI.ARIAUtils.alert(ls`${suggestion}, suggestion`, this._element);
|
|
this._suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestion);
|
|
|
|
return this.visible() && !!suggestion;
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
acceptSuggestion() {
|
|
const result = this._applySuggestion();
|
|
this.hide();
|
|
if (!result)
|
|
return false;
|
|
|
|
this._suggestBoxDelegate.acceptSuggestion();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!UI.SuggestBox.Suggestion} item
|
|
* @return {!Element}
|
|
*/
|
|
createElementForItem(item) {
|
|
const query = this._userEnteredText;
|
|
const element = createElementWithClass('div', 'suggest-box-content-item source-code');
|
|
if (item.iconType) {
|
|
const icon = UI.Icon.create(item.iconType, 'suggestion-icon');
|
|
element.appendChild(icon);
|
|
}
|
|
if (item.isSecondary)
|
|
element.classList.add('secondary');
|
|
element.tabIndex = -1;
|
|
const maxTextLength = 50 + query.length;
|
|
const displayText = (item.title || item.text).trimEnd(maxTextLength);
|
|
|
|
const titleElement = element.createChild('span', 'suggestion-title');
|
|
const index = displayText.toLowerCase().indexOf(query.toLowerCase());
|
|
if (index > 0)
|
|
titleElement.createChild('span').textContent = displayText.substring(0, index);
|
|
if (index > -1)
|
|
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 (item.subtitle) {
|
|
const subtitleElement = element.createChild('span', 'suggestion-subtitle');
|
|
subtitleElement.textContent = item.subtitle.trimEnd(maxTextLength - displayText.length);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!UI.SuggestBox.Suggestion} item
|
|
* @return {number}
|
|
*/
|
|
heightForItem(item) {
|
|
return this._rowHeight;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!UI.SuggestBox.Suggestion} item
|
|
* @return {boolean}
|
|
*/
|
|
isItemSelectable(item) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @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');
|
|
if (fromElement || this._userInteracted || !this._defaultSelectionIsDimmed)
|
|
toElement.classList.add('force-white-icons');
|
|
}
|
|
this._applySuggestion(true);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_onClick(event) {
|
|
const item = this._list.itemForNode(/** @type {?Node} */ (event.target));
|
|
if (!item)
|
|
return;
|
|
|
|
this._list.selectItem(item);
|
|
this._setUserInteracted(true);
|
|
this.acceptSuggestion();
|
|
event.consume(true);
|
|
}
|
|
|
|
/**
|
|
* @param {!UI.SuggestBox.Suggestions} completions
|
|
* @param {boolean} canShowForSingleItem
|
|
* @param {string} userEnteredText
|
|
* @return {boolean}
|
|
*/
|
|
_canShowBox(completions, canShowForSingleItem, userEnteredText) {
|
|
if (!completions || !completions.length)
|
|
return false;
|
|
|
|
if (completions.length > 1)
|
|
return true;
|
|
|
|
if (!completions[0].text.startsWith(userEnteredText))
|
|
return true;
|
|
|
|
// Do not show a single suggestion if it is the same as user-entered query, even if allowed to show single-item suggest boxes.
|
|
return canShowForSingleItem && completions[0].text !== userEnteredText;
|
|
}
|
|
|
|
/**
|
|
* @param {!AnchorBox} anchorBox
|
|
* @param {!UI.SuggestBox.Suggestions} completions
|
|
* @param {boolean} selectHighestPriority
|
|
* @param {boolean} canShowForSingleItem
|
|
* @param {string} userEnteredText
|
|
*/
|
|
updateSuggestions(anchorBox, completions, selectHighestPriority, canShowForSingleItem, userEnteredText) {
|
|
this._onlyCompletion = null;
|
|
if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) {
|
|
this._userEnteredText = userEnteredText;
|
|
|
|
this._show();
|
|
this._updateMaxSize(completions);
|
|
this._glassPane.setContentAnchorBox(anchorBox);
|
|
this._list.invalidateItemHeight();
|
|
this._items.replaceAll(completions);
|
|
|
|
if (selectHighestPriority) {
|
|
let highestPriorityItem = completions[0];
|
|
let highestPriority = completions[0].priority || 0;
|
|
for (let i = 0; i < completions.length; i++) {
|
|
const priority = completions[i].priority || 0;
|
|
if (highestPriority < priority) {
|
|
highestPriority = priority;
|
|
highestPriorityItem = completions[i];
|
|
}
|
|
}
|
|
this._list.selectItem(highestPriorityItem, true);
|
|
} else {
|
|
this._list.selectItem(null);
|
|
}
|
|
} else {
|
|
if (completions.length === 1) {
|
|
this._onlyCompletion = completions[0].text;
|
|
this._applySuggestion(true);
|
|
}
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!KeyboardEvent} event
|
|
* @return {boolean}
|
|
*/
|
|
keyPressed(event) {
|
|
let selected = false;
|
|
switch (event.key) {
|
|
case 'Enter':
|
|
return this.enterKeyPressed();
|
|
case 'ArrowUp':
|
|
selected = this._list.selectPreviousItem(true, false);
|
|
break;
|
|
case 'ArrowDown':
|
|
selected = this._list.selectNextItem(true, false);
|
|
break;
|
|
case 'PageUp':
|
|
selected = this._list.selectItemPreviousPage(false);
|
|
break;
|
|
case 'PageDown':
|
|
selected = this._list.selectItemNextPage(false);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (selected) {
|
|
this._setUserInteracted(true);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
enterKeyPressed() {
|
|
if (!this._userInteracted && this._captureEnter)
|
|
return false;
|
|
|
|
const 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;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @typedef {!{text: string, subtitle: (string|undefined), iconType: (string|undefined), priority: (number|undefined), isSecondary: (boolean|undefined), title: (string|undefined)}}
|
|
*/
|
|
UI.SuggestBox.Suggestion;
|
|
|
|
/**
|
|
* @typedef {!Array<!UI.SuggestBox.Suggestion>}
|
|
*/
|
|
UI.SuggestBox.Suggestions;
|