| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as dom from '../../dom.js';
- import { DomEmitter } from '../../event.js';
- import { renderFormattedText, renderText } from '../../formattedTextRenderer.js';
- import { ActionBar } from '../actionbar/actionbar.js';
- import * as aria from '../aria/aria.js';
- import { ScrollableElement } from '../scrollbar/scrollableElement.js';
- import { Widget } from '../widget.js';
- import { Color } from '../../../common/color.js';
- import { Emitter, Event } from '../../../common/event.js';
- import { HistoryNavigator } from '../../../common/history.js';
- import { mixin } from '../../../common/objects.js';
- import './inputBox.css';
- import * as nls from '../../../../nls.js';
- const $ = dom.$;
- const defaultOpts = {
- inputBackground: Color.fromHex('#3C3C3C'),
- inputForeground: Color.fromHex('#CCCCCC'),
- inputValidationInfoBorder: Color.fromHex('#55AAFF'),
- inputValidationInfoBackground: Color.fromHex('#063B49'),
- inputValidationWarningBorder: Color.fromHex('#B89500'),
- inputValidationWarningBackground: Color.fromHex('#352A05'),
- inputValidationErrorBorder: Color.fromHex('#BE1100'),
- inputValidationErrorBackground: Color.fromHex('#5A1D1D')
- };
- export class InputBox extends Widget {
- constructor(container, contextViewProvider, options) {
- var _a;
- super();
- this.state = 'idle';
- this.maxHeight = Number.POSITIVE_INFINITY;
- this._onDidChange = this._register(new Emitter());
- this.onDidChange = this._onDidChange.event;
- this._onDidHeightChange = this._register(new Emitter());
- this.onDidHeightChange = this._onDidHeightChange.event;
- this.contextViewProvider = contextViewProvider;
- this.options = options || Object.create(null);
- mixin(this.options, defaultOpts, false);
- this.message = null;
- this.placeholder = this.options.placeholder || '';
- this.tooltip = (_a = this.options.tooltip) !== null && _a !== void 0 ? _a : (this.placeholder || '');
- this.ariaLabel = this.options.ariaLabel || '';
- this.inputBackground = this.options.inputBackground;
- this.inputForeground = this.options.inputForeground;
- this.inputBorder = this.options.inputBorder;
- this.inputValidationInfoBorder = this.options.inputValidationInfoBorder;
- this.inputValidationInfoBackground = this.options.inputValidationInfoBackground;
- this.inputValidationInfoForeground = this.options.inputValidationInfoForeground;
- this.inputValidationWarningBorder = this.options.inputValidationWarningBorder;
- this.inputValidationWarningBackground = this.options.inputValidationWarningBackground;
- this.inputValidationWarningForeground = this.options.inputValidationWarningForeground;
- this.inputValidationErrorBorder = this.options.inputValidationErrorBorder;
- this.inputValidationErrorBackground = this.options.inputValidationErrorBackground;
- this.inputValidationErrorForeground = this.options.inputValidationErrorForeground;
- if (this.options.validationOptions) {
- this.validation = this.options.validationOptions.validation;
- }
- this.element = dom.append(container, $('.monaco-inputbox.idle'));
- const tagName = this.options.flexibleHeight ? 'textarea' : 'input';
- const wrapper = dom.append(this.element, $('.ibwrapper'));
- this.input = dom.append(wrapper, $(tagName + '.input.empty'));
- this.input.setAttribute('autocorrect', 'off');
- this.input.setAttribute('autocapitalize', 'off');
- this.input.setAttribute('spellcheck', 'false');
- this.onfocus(this.input, () => this.element.classList.add('synthetic-focus'));
- this.onblur(this.input, () => this.element.classList.remove('synthetic-focus'));
- if (this.options.flexibleHeight) {
- this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY;
- this.mirror = dom.append(wrapper, $('div.mirror'));
- this.mirror.innerText = '\u00a0';
- this.scrollableElement = new ScrollableElement(this.element, { vertical: 1 /* ScrollbarVisibility.Auto */ });
- if (this.options.flexibleWidth) {
- this.input.setAttribute('wrap', 'off');
- this.mirror.style.whiteSpace = 'pre';
- this.mirror.style.wordWrap = 'initial';
- }
- dom.append(container, this.scrollableElement.getDomNode());
- this._register(this.scrollableElement);
- // from ScrollableElement to DOM
- this._register(this.scrollableElement.onScroll(e => this.input.scrollTop = e.scrollTop));
- const onSelectionChange = this._register(new DomEmitter(document, 'selectionchange'));
- const onAnchoredSelectionChange = Event.filter(onSelectionChange.event, () => {
- const selection = document.getSelection();
- return (selection === null || selection === void 0 ? void 0 : selection.anchorNode) === wrapper;
- });
- // from DOM to ScrollableElement
- this._register(onAnchoredSelectionChange(this.updateScrollDimensions, this));
- this._register(this.onDidHeightChange(this.updateScrollDimensions, this));
- }
- else {
- this.input.type = this.options.type || 'text';
- this.input.setAttribute('wrap', 'off');
- }
- if (this.ariaLabel) {
- this.input.setAttribute('aria-label', this.ariaLabel);
- }
- if (this.placeholder && !this.options.showPlaceholderOnFocus) {
- this.setPlaceHolder(this.placeholder);
- }
- if (this.tooltip) {
- this.setTooltip(this.tooltip);
- }
- this.oninput(this.input, () => this.onValueChange());
- this.onblur(this.input, () => this.onBlur());
- this.onfocus(this.input, () => this.onFocus());
- this.ignoreGesture(this.input);
- setTimeout(() => this.updateMirror(), 0);
- // Support actions
- if (this.options.actions) {
- this.actionbar = this._register(new ActionBar(this.element));
- this.actionbar.push(this.options.actions, { icon: true, label: false });
- }
- this.applyStyles();
- }
- onBlur() {
- this._hideMessage();
- if (this.options.showPlaceholderOnFocus) {
- this.input.setAttribute('placeholder', '');
- }
- }
- onFocus() {
- this._showMessage();
- if (this.options.showPlaceholderOnFocus) {
- this.input.setAttribute('placeholder', this.placeholder || '');
- }
- }
- setPlaceHolder(placeHolder) {
- this.placeholder = placeHolder;
- this.input.setAttribute('placeholder', placeHolder);
- }
- setTooltip(tooltip) {
- this.tooltip = tooltip;
- this.input.title = tooltip;
- }
- setAriaLabel(label) {
- this.ariaLabel = label;
- if (label) {
- this.input.setAttribute('aria-label', this.ariaLabel);
- }
- else {
- this.input.removeAttribute('aria-label');
- }
- }
- getAriaLabel() {
- return this.ariaLabel;
- }
- get inputElement() {
- return this.input;
- }
- get value() {
- return this.input.value;
- }
- set value(newValue) {
- if (this.input.value !== newValue) {
- this.input.value = newValue;
- this.onValueChange();
- }
- }
- get height() {
- return typeof this.cachedHeight === 'number' ? this.cachedHeight : dom.getTotalHeight(this.element);
- }
- focus() {
- this.input.focus();
- }
- blur() {
- this.input.blur();
- }
- hasFocus() {
- return document.activeElement === this.input;
- }
- select(range = null) {
- this.input.select();
- if (range) {
- this.input.setSelectionRange(range.start, range.end);
- if (range.end === this.input.value.length) {
- this.input.scrollLeft = this.input.scrollWidth;
- }
- }
- }
- isSelectionAtEnd() {
- return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd;
- }
- enable() {
- this.input.removeAttribute('disabled');
- }
- disable() {
- this.blur();
- this.input.disabled = true;
- this._hideMessage();
- }
- get width() {
- return dom.getTotalWidth(this.input);
- }
- set width(width) {
- if (this.options.flexibleHeight && this.options.flexibleWidth) {
- // textarea with horizontal scrolling
- let horizontalPadding = 0;
- if (this.mirror) {
- const paddingLeft = parseFloat(this.mirror.style.paddingLeft || '') || 0;
- const paddingRight = parseFloat(this.mirror.style.paddingRight || '') || 0;
- horizontalPadding = paddingLeft + paddingRight;
- }
- this.input.style.width = (width - horizontalPadding) + 'px';
- }
- else {
- this.input.style.width = width + 'px';
- }
- if (this.mirror) {
- this.mirror.style.width = width + 'px';
- }
- }
- set paddingRight(paddingRight) {
- // Set width to avoid hint text overlapping buttons
- this.input.style.width = `calc(100% - ${paddingRight}px)`;
- if (this.mirror) {
- this.mirror.style.paddingRight = paddingRight + 'px';
- }
- }
- updateScrollDimensions() {
- if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number' || !this.scrollableElement) {
- return;
- }
- const scrollHeight = this.cachedContentHeight;
- const height = this.cachedHeight;
- const scrollTop = this.input.scrollTop;
- this.scrollableElement.setScrollDimensions({ scrollHeight, height });
- this.scrollableElement.setScrollPosition({ scrollTop });
- }
- showMessage(message, force) {
- this.message = message;
- this.element.classList.remove('idle');
- this.element.classList.remove('info');
- this.element.classList.remove('warning');
- this.element.classList.remove('error');
- this.element.classList.add(this.classForType(message.type));
- const styles = this.stylesForType(this.message.type);
- this.element.style.border = styles.border ? `1px solid ${styles.border}` : '';
- if (this.hasFocus() || force) {
- this._showMessage();
- }
- }
- hideMessage() {
- this.message = null;
- this.element.classList.remove('info');
- this.element.classList.remove('warning');
- this.element.classList.remove('error');
- this.element.classList.add('idle');
- this._hideMessage();
- this.applyStyles();
- }
- validate() {
- let errorMsg = null;
- if (this.validation) {
- errorMsg = this.validation(this.value);
- if (errorMsg) {
- this.inputElement.setAttribute('aria-invalid', 'true');
- this.showMessage(errorMsg);
- }
- else if (this.inputElement.hasAttribute('aria-invalid')) {
- this.inputElement.removeAttribute('aria-invalid');
- this.hideMessage();
- }
- }
- return errorMsg === null || errorMsg === void 0 ? void 0 : errorMsg.type;
- }
- stylesForType(type) {
- switch (type) {
- case 1 /* MessageType.INFO */: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground };
- case 2 /* MessageType.WARNING */: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground };
- default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground, foreground: this.inputValidationErrorForeground };
- }
- }
- classForType(type) {
- switch (type) {
- case 1 /* MessageType.INFO */: return 'info';
- case 2 /* MessageType.WARNING */: return 'warning';
- default: return 'error';
- }
- }
- _showMessage() {
- if (!this.contextViewProvider || !this.message) {
- return;
- }
- let div;
- const layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px';
- this.contextViewProvider.showContextView({
- getAnchor: () => this.element,
- anchorAlignment: 1 /* AnchorAlignment.RIGHT */,
- render: (container) => {
- if (!this.message) {
- return null;
- }
- div = dom.append(container, $('.monaco-inputbox-container'));
- layout();
- const renderOptions = {
- inline: true,
- className: 'monaco-inputbox-message'
- };
- const spanElement = (this.message.formatContent
- ? renderFormattedText(this.message.content, renderOptions)
- : renderText(this.message.content, renderOptions));
- spanElement.classList.add(this.classForType(this.message.type));
- const styles = this.stylesForType(this.message.type);
- spanElement.style.backgroundColor = styles.background ? styles.background.toString() : '';
- spanElement.style.color = styles.foreground ? styles.foreground.toString() : '';
- spanElement.style.border = styles.border ? `1px solid ${styles.border}` : '';
- dom.append(div, spanElement);
- return null;
- },
- onHide: () => {
- this.state = 'closed';
- },
- layout: layout
- });
- // ARIA Support
- let alertText;
- if (this.message.type === 3 /* MessageType.ERROR */) {
- alertText = nls.localize('alertErrorMessage', "Error: {0}", this.message.content);
- }
- else if (this.message.type === 2 /* MessageType.WARNING */) {
- alertText = nls.localize('alertWarningMessage', "Warning: {0}", this.message.content);
- }
- else {
- alertText = nls.localize('alertInfoMessage', "Info: {0}", this.message.content);
- }
- aria.alert(alertText);
- this.state = 'open';
- }
- _hideMessage() {
- if (!this.contextViewProvider) {
- return;
- }
- if (this.state === 'open') {
- this.contextViewProvider.hideContextView();
- }
- this.state = 'idle';
- }
- onValueChange() {
- this._onDidChange.fire(this.value);
- this.validate();
- this.updateMirror();
- this.input.classList.toggle('empty', !this.value);
- if (this.state === 'open' && this.contextViewProvider) {
- this.contextViewProvider.layout();
- }
- }
- updateMirror() {
- if (!this.mirror) {
- return;
- }
- const value = this.value;
- const lastCharCode = value.charCodeAt(value.length - 1);
- const suffix = lastCharCode === 10 ? ' ' : '';
- const mirrorTextContent = (value + suffix)
- .replace(/\u000c/g, ''); // Don't measure with the form feed character, which messes up sizing
- if (mirrorTextContent) {
- this.mirror.textContent = value + suffix;
- }
- else {
- this.mirror.innerText = '\u00a0';
- }
- this.layout();
- }
- style(styles) {
- this.inputBackground = styles.inputBackground;
- this.inputForeground = styles.inputForeground;
- this.inputBorder = styles.inputBorder;
- this.inputValidationInfoBackground = styles.inputValidationInfoBackground;
- this.inputValidationInfoForeground = styles.inputValidationInfoForeground;
- this.inputValidationInfoBorder = styles.inputValidationInfoBorder;
- this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
- this.inputValidationWarningForeground = styles.inputValidationWarningForeground;
- this.inputValidationWarningBorder = styles.inputValidationWarningBorder;
- this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
- this.inputValidationErrorForeground = styles.inputValidationErrorForeground;
- this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
- this.applyStyles();
- }
- applyStyles() {
- const background = this.inputBackground ? this.inputBackground.toString() : '';
- const foreground = this.inputForeground ? this.inputForeground.toString() : '';
- const border = this.inputBorder ? this.inputBorder.toString() : '';
- this.element.style.backgroundColor = background;
- this.element.style.color = foreground;
- this.input.style.backgroundColor = 'inherit';
- this.input.style.color = foreground;
- this.element.style.borderWidth = border ? '1px' : '';
- this.element.style.borderStyle = border ? 'solid' : '';
- this.element.style.borderColor = border;
- }
- layout() {
- if (!this.mirror) {
- return;
- }
- const previousHeight = this.cachedContentHeight;
- this.cachedContentHeight = dom.getTotalHeight(this.mirror);
- if (previousHeight !== this.cachedContentHeight) {
- this.cachedHeight = Math.min(this.cachedContentHeight, this.maxHeight);
- this.input.style.height = this.cachedHeight + 'px';
- this._onDidHeightChange.fire(this.cachedContentHeight);
- }
- }
- insertAtCursor(text) {
- const inputElement = this.inputElement;
- const start = inputElement.selectionStart;
- const end = inputElement.selectionEnd;
- const content = inputElement.value;
- if (start !== null && end !== null) {
- this.value = content.substr(0, start) + text + content.substr(end);
- inputElement.setSelectionRange(start + 1, start + 1);
- this.layout();
- }
- }
- dispose() {
- this._hideMessage();
- this.message = null;
- if (this.actionbar) {
- this.actionbar.dispose();
- }
- super.dispose();
- }
- }
- export class HistoryInputBox extends InputBox {
- constructor(container, contextViewProvider, options) {
- const NLS_PLACEHOLDER_HISTORY_HINT = nls.localize({ key: 'history.inputbox.hint', comment: ['Text will be prefixed with \u21C5 plus a single space, then used as a hint where input field keeps history'] }, "for history");
- const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX = ` or \u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT}`;
- const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS = ` (\u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT})`;
- super(container, contextViewProvider, options);
- this._onDidFocus = this._register(new Emitter());
- this.onDidFocus = this._onDidFocus.event;
- this._onDidBlur = this._register(new Emitter());
- this.onDidBlur = this._onDidBlur.event;
- this.history = new HistoryNavigator(options.history, 100);
- // Function to append the history suffix to the placeholder if necessary
- const addSuffix = () => {
- if (options.showHistoryHint && options.showHistoryHint() && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX) && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS) && this.history.getHistory().length) {
- const suffix = this.placeholder.endsWith(')') ? NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX : NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS;
- const suffixedPlaceholder = this.placeholder + suffix;
- if (options.showPlaceholderOnFocus && document.activeElement !== this.input) {
- this.placeholder = suffixedPlaceholder;
- }
- else {
- this.setPlaceHolder(suffixedPlaceholder);
- }
- }
- };
- // Spot the change to the textarea class attribute which occurs when it changes between non-empty and empty,
- // and add the history suffix to the placeholder if not yet present
- this.observer = new MutationObserver((mutationList, observer) => {
- mutationList.forEach((mutation) => {
- if (!mutation.target.textContent) {
- addSuffix();
- }
- });
- });
- this.observer.observe(this.input, { attributeFilter: ['class'] });
- this.onfocus(this.input, () => addSuffix());
- this.onblur(this.input, () => {
- const resetPlaceholder = (historyHint) => {
- if (!this.placeholder.endsWith(historyHint)) {
- return false;
- }
- else {
- const revertedPlaceholder = this.placeholder.slice(0, this.placeholder.length - historyHint.length);
- if (options.showPlaceholderOnFocus) {
- this.placeholder = revertedPlaceholder;
- }
- else {
- this.setPlaceHolder(revertedPlaceholder);
- }
- return true;
- }
- };
- if (!resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS)) {
- resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX);
- }
- });
- }
- dispose() {
- super.dispose();
- if (this.observer) {
- this.observer.disconnect();
- this.observer = undefined;
- }
- }
- addToHistory() {
- if (this.value && this.value !== this.getCurrentValue()) {
- this.history.add(this.value);
- }
- }
- showNextValue() {
- if (!this.history.has(this.value)) {
- this.addToHistory();
- }
- let next = this.getNextValue();
- if (next) {
- next = next === this.value ? this.getNextValue() : next;
- }
- if (next) {
- this.value = next;
- aria.status(this.value);
- }
- }
- showPreviousValue() {
- if (!this.history.has(this.value)) {
- this.addToHistory();
- }
- let previous = this.getPreviousValue();
- if (previous) {
- previous = previous === this.value ? this.getPreviousValue() : previous;
- }
- if (previous) {
- this.value = previous;
- aria.status(this.value);
- }
- }
- onBlur() {
- super.onBlur();
- this._onDidBlur.fire();
- }
- onFocus() {
- super.onFocus();
- this._onDidFocus.fire();
- }
- getCurrentValue() {
- let currentValue = this.history.current();
- if (!currentValue) {
- currentValue = this.history.last();
- this.history.next();
- }
- return currentValue;
- }
- getPreviousValue() {
- return this.history.previous() || this.history.first();
- }
- getNextValue() {
- return this.history.next() || this.history.last();
- }
- }
|