/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { InputBox as vsInputBox, IInputOptions as vsIInputBoxOptions, IInputBoxStyles as vsIInputBoxStyles, IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { Color } from 'vs/base/common/color'; import { Event, Emitter } from 'vs/base/common/event'; import { AdsWidget } from 'sql/base/browser/ui/adsWidget'; export interface OnLoseFocusParams { value: string; hasChanged: boolean; } export interface IInputBoxStyles extends vsIInputBoxStyles { disabledInputBackground?: Color; disabledInputForeground?: Color; } export interface IInputOptions extends vsIInputBoxOptions { /** * Whether calls to validate require the force parameter to be set to true * to run the base VS Input Box validation logic. See validate() override * for more info. */ requireForceValidations?: boolean; required?: boolean; ariaDescription?: string; } export class InputBox extends vsInputBox implements AdsWidget { private enabledInputBackground?: Color; private enabledInputForeground?: Color; private enabledInputBorder?: Color; private disabledInputBackground?: Color; private disabledInputForeground?: Color; private disabledInputBorder?: Color; private _lastLoseFocusValue: string; private _onLoseFocus = this._register(new Emitter()); public onLoseFocus: Event = this._onLoseFocus.event; private _onInputFocus = this._register(new Emitter()); public onInputFocus: Event = this._onInputFocus.event; private _isTextAreaInput = false; private _hideErrors = false; constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, private _sqlOptions?: IInputOptions, id?: string) { super(container, contextViewProvider, _sqlOptions); this.enabledInputBackground = this.inputBackground; this.enabledInputForeground = this.inputForeground; this.enabledInputBorder = this.inputBorder; this.disabledInputBackground = Color.transparent; this._lastLoseFocusValue = this.value; let self = this; this.onblur(this.inputElement, () => { self._onLoseFocus.fire({ value: self.value, hasChanged: self._lastLoseFocusValue !== self.value }); self._lastLoseFocusValue = self.value; }); this.onfocus(this.inputElement, () => { self._onInputFocus.fire(); }); if (_sqlOptions && _sqlOptions.type === 'textarea') { this._isTextAreaInput = true; } this.required = !!this._sqlOptions?.required; if (this._sqlOptions.ariaDescription) { this.inputElement.setAttribute('aria-description', this._sqlOptions.ariaDescription); } this.inputElement.id = id; } public override style(styles: IInputBoxStyles): void { super.style(styles); this.enabledInputBackground = this.inputBackground; this.enabledInputForeground = this.inputForeground; this.enabledInputBorder = this.inputBorder; this.disabledInputBackground = styles.disabledInputBackground; this.disabledInputForeground = styles.disabledInputForeground; this.updateInputEnabledDisabledColors(); this.applyStyles(); } public override enable(): void { super.enable(); this.updateInputEnabledDisabledColors(); this.applyStyles(); } public set rows(value: number) { this.inputElement.setAttribute('rows', value.toString()); } public set columns(value: number) { this.inputElement.setAttribute('cols', value.toString()); } public override layout(): void { if (!this._isTextAreaInput) { super.layout(); } } public override disable(): void { super.disable(); this.updateInputEnabledDisabledColors(); this.applyStyles(); } public setHeight(value: string) { if (this._isTextAreaInput) { this.inputElement.style.height = value; this.inputElement.style.whiteSpace = 'normal'; } } public setMaxLength(value: number | undefined) { if (value === undefined) { this.inputElement.removeAttribute('maxLength'); } else { this.inputElement.maxLength = value; } } public set ariaLive(value: string) { this.element.setAttribute('aria-live', value); } public isEnabled(): boolean { return !this.inputElement.hasAttribute('disabled'); } public get required(): boolean { return this.inputElement.required; } public set required(v: boolean) { this.inputElement.required = v; } public get hideErrors(): boolean { return this._hideErrors; } public set hideErrors(hideErrors: boolean) { this._hideErrors = hideErrors; if (hideErrors) { this.hideMessage(); } } public override showMessage(message: IMessage, force?: boolean): void { if (!this.hideErrors) { super.showMessage(message, force); } } private updateInputEnabledDisabledColors(): void { let enabled = this.isEnabled(); this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground; this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground; this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder; } public override validate(force?: boolean): MessageType | undefined { // We override the validate call here because in some situations we could end up with an "invalid" alert // being announced incorrectly. For example the InputBox component has its own async validation - and so // if a change was made to the text then the base VS InputBox would call validate immediately - before // the async validation was able to trigger and complete and so the state could still be invalid at that // point // So instead we allow users of the input box to control whether to let the base input box do its validation // as normal or whether to require manually calling validate with force === true in order to run the validation // logic. if (force || this._sqlOptions?.requireForceValidations !== true) { return super.validate(); } return undefined; } public override set width(width: number) { super.width = width; this.element.style.width = 'fit-content'; } public override get value() { return super.value; } public override set value(newValue: string) { this._lastLoseFocusValue = newValue; super.value = newValue; } public get id(): string { return this.input.id; } }