@quick-game/cli
Version:
Command line interface for rapid qg development
295 lines • 12.8 kB
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.
*/
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as Utils from './utils/utils.js';
import * as ARIAUtils from './ARIAUtils.js';
import { Size } from './Geometry.js';
import { GlassPane } from './GlassPane.js';
import { ListControl, ListMode } from './ListControl.js';
import { ListModel } from './ListModel.js';
import { measurePreferredSize } from './UIUtils.js';
import suggestBoxStyles from './suggestBox.css.legacy.js';
const UIStrings = {
/**
*@description Aria alert to read the suggestion for the suggestion box when typing in text editor
*@example {name} PH1
*@example {2} PH2
*@example {5} PH3
*/
sSuggestionSOfS: '{PH1}, suggestion {PH2} of {PH3}',
/**
*@description Aria alert to confirm the suggestion when it is selected from the suggestion box
*@example {name} PH1
*/
sSuggestionSSelected: '{PH1}, suggestion selected',
};
const str_ = i18n.i18n.registerUIStrings('ui/legacy/SuggestBox.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class SuggestBox {
suggestBoxDelegate;
maxItemsHeight;
rowHeight;
userEnteredText;
defaultSelectionIsDimmed;
onlyCompletion;
items;
list;
element;
glassPane;
constructor(suggestBoxDelegate, maxItemsHeight) {
this.suggestBoxDelegate = suggestBoxDelegate;
this.maxItemsHeight = maxItemsHeight;
this.rowHeight = 17;
this.userEnteredText = '';
this.defaultSelectionIsDimmed = false;
this.onlyCompletion = null;
this.items = new ListModel();
this.list = new ListControl(this.items, this, 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 GlassPane();
this.glassPane.setAnchorBehavior("PreferBottom" /* AnchorBehavior.PreferBottom */);
this.glassPane.setOutsideClickCallback(this.hide.bind(this));
const shadowRoot = Utils.createShadowRootWithCoreStyles(this.glassPane.contentElement, { cssFile: suggestBoxStyles, delegatesFocus: undefined });
shadowRoot.appendChild(this.element);
}
visible() {
return this.glassPane.isShowing();
}
setPosition(anchorBox) {
this.glassPane.setContentAnchorBox(anchorBox);
}
setAnchorBehavior(behavior) {
this.glassPane.setAnchorBehavior(behavior);
}
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 Size(maxWidth, maxHeight));
}
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(maxItem);
const preferredWidth = measurePreferredSize(element, this.element).width + Utils.measuredScrollbarWidth(this.element.ownerDocument);
return Math.min(kMaxWidth, preferredWidth);
}
show() {
if (this.visible()) {
return;
}
// TODO(dgozman): take document as a parameter.
this.glassPane.show(document);
const suggestion = { text: '1', subtitle: '12' };
this.rowHeight = measurePreferredSize(this.createElementForItem(suggestion), this.element).height;
ARIAUtils.setControls(this.suggestBoxDelegate.ariaControlledBy(), this.element);
ARIAUtils.setExpanded(this.suggestBoxDelegate.ariaControlledBy(), true);
}
hide() {
if (!this.visible()) {
return;
}
this.glassPane.hide();
ARIAUtils.setControls(this.suggestBoxDelegate.ariaControlledBy(), null);
ARIAUtils.setExpanded(this.suggestBoxDelegate.ariaControlledBy(), false);
}
applySuggestion(isIntermediateSuggestion) {
if (this.onlyCompletion) {
isIntermediateSuggestion ?
ARIAUtils.alert(i18nString(UIStrings.sSuggestionSOfS, { PH1: this.onlyCompletion.text, PH2: this.list.selectedIndex() + 1, PH3: this.items.length })) :
ARIAUtils.alert(i18nString(UIStrings.sSuggestionSSelected, { PH1: this.onlyCompletion.text }));
this.suggestBoxDelegate.applySuggestion(this.onlyCompletion, isIntermediateSuggestion);
return true;
}
const suggestion = this.list.selectedItem();
if (suggestion && suggestion.text) {
isIntermediateSuggestion ?
ARIAUtils.alert(i18nString(UIStrings.sSuggestionSOfS, {
PH1: suggestion.title || suggestion.text,
PH2: this.list.selectedIndex() + 1,
PH3: this.items.length,
})) :
ARIAUtils.alert(i18nString(UIStrings.sSuggestionSSelected, { PH1: suggestion.title || suggestion.text }));
}
this.suggestBoxDelegate.applySuggestion(suggestion, isIntermediateSuggestion);
return this.visible() && Boolean(suggestion);
}
acceptSuggestion() {
const result = this.applySuggestion();
this.hide();
if (!result) {
return false;
}
this.suggestBoxDelegate.acceptSuggestion();
return true;
}
createElementForItem(item) {
const query = this.userEnteredText;
const element = document.createElement('div');
element.classList.add('suggest-box-content-item');
element.classList.add('source-code');
if (item.isSecondary) {
element.classList.add('secondary');
}
element.tabIndex = -1;
const maxTextLength = 50 + query.length;
const displayText = Platform.StringUtilities.trimEndWithMaxLength((item.title || item.text).trim(), maxTextLength)
.replace(/\n/g, '\u21B5');
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.subtitleRenderer) {
const subtitleElement = item.subtitleRenderer.call(null);
subtitleElement.classList.add('suggestion-subtitle');
element.appendChild(subtitleElement);
}
else if (item.subtitle) {
const subtitleElement = element.createChild('span', 'suggestion-subtitle');
subtitleElement.textContent =
Platform.StringUtilities.trimEndWithMaxLength(item.subtitle, maxTextLength - displayText.length);
}
if (item.iconElement) {
element.appendChild(item.iconElement);
}
return element;
}
heightForItem(_item) {
return this.rowHeight;
}
isItemSelectable(_item) {
return true;
}
selectedItemChanged(from, to, fromElement, toElement) {
if (fromElement) {
fromElement.classList.remove('selected', 'force-white-icons');
}
if (toElement) {
toElement.classList.add('selected');
toElement.classList.add('force-white-icons');
}
this.applySuggestion(true);
}
updateSelectedItemARIA(_fromElement, _toElement) {
return false;
}
onClick(event) {
const item = this.list.itemForNode(event.target);
if (!item) {
return;
}
this.list.selectItem(item);
this.acceptSuggestion();
event.consume(true);
}
canShowBox(completions, highestPriorityItem, canShowForSingleItem, userEnteredText) {
if (!completions || !completions.length) {
return false;
}
if (completions.length > 1) {
return true;
}
if (!highestPriorityItem || highestPriorityItem.isSecondary ||
!highestPriorityItem.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 && highestPriorityItem.text !== userEnteredText;
}
updateSuggestions(anchorBox, completions, selectHighestPriority, canShowForSingleItem, userEnteredText) {
this.onlyCompletion = null;
const highestPriorityItem = selectHighestPriority ? completions.reduce((a, b) => (a.priority || 0) >= (b.priority || 0) ? a : b) : null;
if (this.canShowBox(completions, highestPriorityItem, canShowForSingleItem, userEnteredText)) {
this.userEnteredText = userEnteredText;
this.show();
this.updateMaxSize(completions);
this.glassPane.setContentAnchorBox(anchorBox);
this.list.invalidateItemHeight();
this.items.replaceAll(completions);
if (highestPriorityItem && !highestPriorityItem.isSecondary) {
this.list.selectItem(highestPriorityItem, true);
}
else {
this.list.selectItem(null);
}
}
else {
if (completions.length === 1) {
this.onlyCompletion = completions[0];
this.applySuggestion(true);
}
this.hide();
}
}
keyPressed(event) {
switch (event.key) {
case 'Enter':
return this.enterKeyPressed();
case 'ArrowUp':
return this.list.selectPreviousItem(true, false);
case 'ArrowDown':
return this.list.selectNextItem(true, false);
case 'PageUp':
return this.list.selectItemPreviousPage(false);
case 'PageDown':
return this.list.selectItemNextPage(false);
}
return false;
}
enterKeyPressed() {
const hasSelectedItem = Boolean(this.list.selectedItem()) || Boolean(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;
}
}
//# sourceMappingURL=SuggestBox.js.map