novo-elements
Version:
Bullhorn's NOVO Element Repository for Angular 2
496 lines (457 loc) • 29.8 kB
text/typescript
// NG2
import { Component, Input, Output, ElementRef, EventEmitter, OnInit, OnDestroy, Directive, HostListener, AfterContentInit } from '@angular/core';
// Vendor
import { Observable } from 'rxjs/Observable';
// APP
import { NovoFormGroup } from './FormInterfaces';
import { OutsideClick } from '../../utils/outside-click/OutsideClick';
import { NovoLabelService } from '../../services/novo-label-service';
import { Helpers } from '../../utils/Helpers';
import { KeyCodes } from '../../utils/key-codes/KeyCodes';
import { DateFormatService } from '../../services/date-format/DateFormat';
import { FieldInteractionApi } from './FieldInteractionApi';
export interface IMaskOptions {
mask: any;
keepCharPositions: boolean;
guide: boolean;
};
@Directive({
selector: 'textarea[autosize]'
})
export class NovoAutoSize implements AfterContentInit {
@HostListener('input', ['$event.target'])
onInput(textArea: HTMLTextAreaElement): void {
this.adjust();
}
constructor(public element: ElementRef) { }
ngAfterContentInit(): void {
setTimeout(() => {
this.adjust();
});
}
adjust(): void {
let hasValue = this.element.nativeElement.value.length !== 0;
this.element.nativeElement.style.overflow = 'hidden';
if (hasValue) {
this.element.nativeElement.style.height = Math.min((this.element.nativeElement.scrollHeight - 11), 300) + 'px';
} else {
this.element.nativeElement.style.height = '14px';
}
}
}
@Component({
selector: 'novo-custom-control-container',
template: `
<div class="novo-control-container" [hidden]="form.controls[control.key].hidden || form.controls[control.key].type === 'hidden' || form.controls[control.key].controlType === 'hidden'">
<!--Label (for horizontal)-->
<label [attr.for]="control.key" *ngIf="form.layout !== 'vertical' && form.controls[control.key].label">{{ form.controls[control.key].label }}</label>
<div class="novo-control-outer-container">
<!--Label (for vertical)-->
<label
*ngIf="form.layout === 'vertical' && form.controls[control.key].label"
class="novo-control-label"
[attr.for]="control.key"
[class.novo-control-always-active]="true">
{{ form.controls[control.key].label }}
</label>
<div class="novo-control-inner-container">
<div class="novo-control-inner-input-container">
<!--Required Indicator-->
<i [hidden]="!form.controls[control.key].required || form.controls[control.key].readOnly"
class="required-indicator"
[ngClass]="{'bhi-circle': !isValid, 'bhi-check': isValid}" *ngIf="form.controls[control.key].required">
</i>
<!--Form Controls-->
<div class="novo-control-input {{ form.controls[control.key].controlType }}" [attr.data-automation-id]="control.key">
<ng-content></ng-content>
</div>
</div>
<!--Error Message-->
<div class="field-message">
<div class="messages">
<span class="error-text" *ngIf="(form.controls[control.key].dirty || control.dirty) && form.controls[control.key].errors?.required">{{ form.controls[control.key].label | uppercase }} is required</span>
<span class="error-text" *ngIf="(form.controls[control.key].dirty || control.dirty) && (form.controls[control.key].errors?.custom)">{{ form.controls[control.key].errors.custom }}</span>
<!--Field Hint-->
<span class="description" *ngIf="form.controls[control.key].description">
{{ form.controls[control.key].description }}
</span>
</div>
</div>
<!--Tip Wel-->
<novo-tip-well *ngIf="form.controls[control.key].tipWell" [name]="control.key" [tip]="form.controls[control.key]?.tipWell?.tip" [icon]="form.controls[control.key]?.tipWell?.icon" [button]="form.controls[control.key]?.tipWell?.button"></novo-tip-well>
</div>
</div>
</div>
`
})
export class NovoCustomControlContainerElement {
@Input() control;
@Input() form: NovoFormGroup;
}
@Component({
selector: 'novo-control',
template: `
<div class="novo-control-container" [formGroup]="form" [hidden]="form.controls[control.key].hidden || form.controls[control.key].type === 'hidden' || form.controls[control.key].controlType === 'hidden'">
<!--Label (for horizontal)-->
<label [attr.for]="control.key" *ngIf="form.layout !== 'vertical' && form.controls[control.key].label && !condensed">{{ form.controls[control.key].label }}</label>
<div class="novo-control-outer-container">
<!--Label (for vertical)-->
<label
*ngIf="form.layout === 'vertical' && form.controls[control.key].label && !condensed"
class="novo-control-label"
[attr.for]="control.key"
[class.novo-control-empty]="!hasValue"
[class.novo-control-focused]="focused"
[class.novo-control-filled]="hasValue"
[class.novo-control-always-active]="alwaysActive || form.controls[control.key].placeholder"
[class.novo-control-extra-spacing]="requiresExtraSpacing">
{{ form.controls[control.key].label }}
</label>
<div class="novo-control-inner-container">
<div class="novo-control-inner-input-container">
<!--Required Indicator-->
<i [hidden]="!form.controls[control.key].required || form.controls[control.key].readOnly"
class="required-indicator"
[ngClass]="{'bhi-circle': !isValid, 'bhi-check': isValid}" *ngIf="!condensed || form.controls[control.key].required">
</i>
<!--Form Controls-->
<div class="novo-control-input {{ form.controls[control.key].controlType }}" [ngSwitch]="form.controls[control.key].controlType" [attr.data-automation-id]="control.key">
<!--Text-based Inputs-->
<!--TODO prefix/suffix on the control-->
<div class="novo-control-input-container novo-control-input-with-label" *ngSwitchCase="'textbox'" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
<input *ngIf="form.controls[control.key].type !== 'number'" [formControlName]="control.key" [id]="control.key" [type]="form.controls[control.key].type" [placeholder]="form.controls[control.key].placeholder" (input)="emitChange($event)" [maxlength]="form.controls[control.key].maxlength" (focus)="handleFocus($event)" (blur)="handleBlur($event)">
<input *ngIf="form.controls[control.key].type === 'number' && form.controls[control.key].subType !== 'percentage'" [formControlName]="control.key" [id]="control.key" [type]="form.controls[control.key].type" [placeholder]="form.controls[control.key].placeholder" (keydown)="restrictKeys($event)" (input)="emitChange($event)" [maxlength]="form.controls[control.key].maxlength" (focus)="handleFocus($event)" (blur)="handleBlur($event)" step="any" (mousewheel)="numberInput.blur()" #numberInput>
<input *ngIf="form.controls[control.key].type === 'number' && form.controls[control.key].subType === 'percentage'" [type]="form.controls[control.key].type" [placeholder]="form.controls[control.key].placeholder" (keydown)="restrictKeys($event)" [value]="percentValue" (input)="handlePercentChange($event)" (focus)="handleFocus($event)" (blur)="handleBlur($event)" step="any" (mousewheel)="percentInput.blur()" #percentInput>
<label class="input-label" *ngIf="form.controls[control.key].subType === 'currency'">{{ control.currencyFormat }}</label>
<label class="input-label" *ngIf="form.controls[control.key].subType === 'percentage'">%</label>
</div>
<!--TextArea-->
<textarea *ngSwitchCase="'text-area'" [name]="control.key" [attr.id]="control.key" [placeholder]="form.controls[control.key].placeholder" [formControlName]="control.key" autosize (input)="handleTextAreaInput($event)" (focus)="handleFocus($event)" (blur)="handleBlur($event)" [maxlength]="control.maxlength" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></textarea>
<!--Editor-->
<novo-editor *ngSwitchCase="'editor'" [name]="control.key" [formControlName]="control.key" (focus)="handleFocus($event)" (blur)="handleBlur($event)"></novo-editor>
<!--HTML5 Select-->
<select [id]="control.key" *ngSwitchCase="'native-select'" [formControlName]="control.key" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
<option *ngIf="form.controls[control.key].placeholder" value="" disabled selected hidden>{{ form.controls[control.key].placeholder }}</option>
<option *ngFor="let opt of form.controls[control.key].options" [value]="opt.key">{{opt.value}}</option>
</select>
<!--File-->
<novo-file-input *ngSwitchCase="'file'" [formControlName]="control.key" [id]="control.key" [name]="control.key" [placeholder]="form.controls[control.key].placeholder" [value]="form.controls[control.key].value" [multiple]="form.controls[control.key].multiple" [layoutOptions]="form.controls[control.key].layoutOptions" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-file-input>
<!--Tiles-->
<novo-tiles *ngSwitchCase="'tiles'" [options]="control.options" [formControlName]="control.key" (onChange)="modelChange($event)" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-tiles>
<!--Picker-->
<div class="novo-control-input-container" *ngSwitchCase="'picker'">
<novo-picker [config]="form.controls[control.key].config" [formControlName]="control.key" [placeholder]="form.controls[control.key].placeholder" [appendToBody]="form.controls[control.key].appendToBody" [parentScrollSelector]="form.controls[control.key].parentScrollSelector" *ngIf="!form.controls[control.key].multiple" (select)="modelChange($event);" (typing)="handleTyping($event)" (focus)="handleFocus($event)" (blur)="handleBlur($event)" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-picker>
<chips [source]="form.controls[control.key].config" [type]="form.controls[control.key].config.type" [formControlName]="control.key" [placeholder]="form.controls[control.key].placeholder" *ngIf="control.multiple" [closeOnSelect]="form.controls[control.key].closeOnSelect" (changed)="modelChange($event)" (typing)="handleTyping($event)" (focus)="handleFocus($event)" (blur)="handleBlur($event)" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></chips>
</div>
<!--Novo Select-->
<novo-select *ngSwitchCase="'select'" [options]="form.controls[control.key].options" [headerConfig]="form.controls[control.key].headerConfig" [placeholder]="form.controls[control.key].placeholder" [formControlName]="control.key" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition" (onSelect)="modelChange($event)"></novo-select>
<!--Radio-->
<div class="novo-control-input-container" *ngSwitchCase="'radio'">
<novo-radio [vertical]="vertical" [name]="control.key" [formControlName]="control.key" *ngFor="let option of form.controls[control.key].options" [value]="option.value" [label]="option.label" [checked]="option.value === form.value[control.key]" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition" [button]="!!option.icon" [icon]="option.icon" [attr.data-automation-id]="control.key + '-' + (option?.label || option?.value)"></novo-radio>
</div>
<!--Time-->
<div class="novo-control-input-container" *ngSwitchCase="'time'" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
<novo-time-picker-input [attr.id]="control.key" [name]="control.key" [formControlName]="control.key" [placeholder]="form.controls[control.key].placeholder" [military]="form.controls[control.key].military"></novo-time-picker-input>
</div>
<!--Date-->
<div class="novo-control-input-container" *ngSwitchCase="'date'" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
<novo-date-picker-input [attr.id]="control.key" [name]="control.key" [formControlName]="control.key" [placeholder]="form.controls[control.key].placeholder"></novo-date-picker-input>
</div>
<!--Date and Time-->
<div class="novo-control-input-container" *ngSwitchCase="'date-time'" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
<novo-date-time-picker-input [attr.id]="control.key" [name]="control.key" [formControlName]="control.key" [placeholder]="form.controls[control.key].placeholder" [military]="form.controls[control.key].military"></novo-date-time-picker-input>
</div>
<!--Address-->
<novo-address *ngSwitchCase="'address'" [formControlName]="control.key"></novo-address>
<!--Checkbox-->
<novo-checkbox *ngSwitchCase="'checkbox'" [formControlName]="control.key" [name]="control.key" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition" [layoutOptions]="layoutOptions"></novo-checkbox>
<!--Checklist-->
<novo-check-list *ngSwitchCase="'checklist'" [formControlName]="control.key" [name]="control.key" [options]="form.controls[control.key].options" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition" (onSelect)="modelChange($event)"></novo-check-list>
<!--QuickNote-->
<novo-quick-note *ngSwitchCase="'quick-note'" [formControlName]="control.key" [placeholder]="form.controls[control.key].placeholder" [config]="form.controls[control.key].config" (change)="modelChange($event)" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-quick-note>
<!--ReadOnly-->
<!--TODO - Handle rendering of different READONLY values-->
<div *ngSwitchCase="'read-only'">{{ form.value[control.key] }}</div>
</div>
</div>
<!--Error Message-->
<div class="field-message" *ngIf="!condensed" [class.has-tip]="form.controls[control.key].tipWell">
<div class="messages">
<span class="error-text" *ngIf="showFieldMessage"></span>
<span class="error-text" *ngIf="isDirty && errors?.required">{{ form.controls[control.key].label | uppercase }} {{ labels.isRequired }}</span>
<span class="error-text" *ngIf="isDirty && errors?.minlength">{{ form.controls[control.key].label | uppercase }} {{ labels.minLength }} {{ form.controls[control.key].minlength }}</span>
<span class="error-text" *ngIf="isDirty && maxLengthMet && focused && !errors?.maxlength">{{ labels.maxLengthMet }}({{ form.controls[control.key].maxlength }})</span>
<span class="error-text" *ngIf="errors?.maxlength">{{ labels.invalidMaxLength }}({{ form.controls[control.key].maxlength }})</span>
<span class="error-text" *ngIf="isDirty && errors?.invalidEmail">{{ form.controls[control.key].label | uppercase }} {{ labels.invalidEmail }}</span>
<span class="error-text" *ngIf="isDirty && errors?.invalidAddress">{{ form.controls[control.key].label | uppercase }} {{ labels.invalidAddress }}</span>
<span class="error-text" *ngIf="isDirty && (errors?.integerTooLarge || errors?.doubleTooLarge)">{{ form.controls[control.key].label | uppercase }} {{ labels.isTooLarge }}</span>
<span *ngIf="isDirty && errors?.minYear">{{ form.controls[control.key].label | uppercase }} {{ labels.notValidYear }}</span>
<span class="error-text" *ngIf="isDirty && (errors?.custom)">{{ errors.custom }}</span>
<!--Field Hint-->
<span class="description" *ngIf="form.controls[control.key].description">
{{ form.controls[control.key].description }}
</span>
</div>
<span class="character-count" [class.error]="errors?.maxlength" *ngIf="showCount">{{ characterCount }}/{{ form.controls[control.key].maxlength }}</span>
</div>
<!--Tip Wel-->
<novo-tip-well *ngIf="form.controls[control.key].tipWell" [name]="control.key" [tip]="form.controls[control.key]?.tipWell?.tip" [icon]="form.controls[control.key]?.tipWell?.icon" [button]="form.controls[control.key]?.tipWell?.button"></novo-tip-well>
</div>
<i *ngIf="form.controls[control.key].fieldInteractionloading" class="loading">
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="18.2px" height="18.5px" viewBox="0 0 18.2 18.5" style="enable-background:new 0 0 18.2 18.5;"
xml:space="preserve">
<style type="text/css">
.spinner { fill:#FFFFFF; }
</style>
<path class="spinner" d="M9.2,18.5C4.1,18.5,0,14.4,0,9.2S4.1,0,9.2,0c0.9,0,1.9,0.1,2.7,0.4c0.8,0.2,1.2,1.1,1,1.9
c-0.2,0.8-1.1,1.2-1.9,1C10.5,3.1,9.9,3,9.2,3C5.8,3,3,5.8,3,9.2s2.8,6.2,6.2,6.2c2.8,0,5.3-1.9,6-4.7c0.2-0.8,1-1.3,1.8-1.1
c0.8,0.2,1.3,1,1.1,1.8C17.1,15.7,13.4,18.5,9.2,18.5z"/>
</svg>
</i>
</div>
</div>
`,
host: {
'[class]': 'form.controls[control.key].controlType',
'[class.disabled]': 'form.controls[control.key].readOnly',
'[class.hidden]': 'form.controls[control.key].hidden',
'[attr.data-control-key]': 'control.key',
}
})
export class NovoControlElement extends OutsideClick implements OnInit, OnDestroy {
@Input() control;
@Input() form: NovoFormGroup;
@Input() condensed: boolean = false;
@Output() change: EventEmitter<any> = new EventEmitter();
@Output('blur')
get onBlur(): Observable<FocusEvent> {
return this._blurEmitter.asObservable();
}
@Output('focus')
get onFocus(): Observable<FocusEvent> {
return this._focusEmitter.asObservable();
}
private _blurEmitter: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
private _focusEmitter: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
private _focused: boolean = false;
private _enteredText: string = '';
formattedValue: string = '';
percentValue: number;
maxLengthMet: boolean = false;
characterCount: number = 0;
private forceClearSubscription: any;
private percentChangeSubscription: any;
private valueChangeSubscription: any;
private dateChangeSubscription: any;
maskOptions: IMaskOptions;
constructor(element: ElementRef, public labels: NovoLabelService, private dateFormatService: DateFormatService, private fieldInteractionApi: FieldInteractionApi) {
super(element);
}
get showFieldMessage() {
return !this.errors && !this.maxLengthMet && Helpers.isBlank(this.control.description);
}
get showCount() {
return this.form.controls[this.control.key].maxlength && this.focused && (this.form.controls[this.control.key].controlType === 'text-area' || this.form.controls[this.control.key].controlType === 'textbox');
}
ngOnInit() {
// Make sure to initially format the time controls
if (this.control && this.form.controls[this.control.key].value) {
if (this.form.controls[this.control.key].controlType === 'textbox' || this.form.controls[this.control.key].controlType === 'text-area') {
this.characterCount = this.form.controls[this.control.key].value.length;
}
}
if (this.control) {
// Listen to clear events
this.forceClearSubscription = this.control.forceClear.subscribe(() => {
this.clearValue();
});
// Subscribe to control interactions
if (this.control.interactions) {
for (let interaction of this.control.interactions) {
switch (interaction.event) {
case 'blur':
this.valueChangeSubscription = this.onBlur.debounceTime(300).subscribe(() => {
this.executeInteraction(interaction);
});
break;
case 'focus':
this.valueChangeSubscription = this.onFocus.debounceTime(300).subscribe(() => {
this.executeInteraction(interaction);
});
break;
case 'change':
this.valueChangeSubscription = this.form.controls[this.control.key].valueChanges.debounceTime(300).subscribe(() => {
this.executeInteraction(interaction);
});
break;
case 'init':
interaction.invokeOnInit = true;
break;
default:
break;
}
if (interaction.invokeOnInit) {
this.executeInteraction(interaction);
}
}
}
}
if (this.form.controls[this.control.key] && this.form.controls[this.control.key].subType === 'percentage') {
if (!Helpers.isEmpty(this.form.controls[this.control.key].value)) {
this.percentValue = Number((this.form.controls[this.control.key].value * 100).toFixed(6).replace(/\.?0*$/, ''));
}
this.percentChangeSubscription = this.form.controls[this.control.key].displayValueChanges.subscribe(value => {
if (!Helpers.isEmpty(value)) {
this.percentValue = Number((value * 100).toFixed(6).replace(/\.?0*$/, ''));
}
});
}
}
executeInteraction(interaction) {
if (interaction.script && Helpers.isFunction(interaction.script)) {
setTimeout(() => {
this.fieldInteractionApi.form = this.form;
this.fieldInteractionApi.currentKey = this.control.key;
try {
interaction.script(this.fieldInteractionApi, this.control.key);
} catch (err) {
console.info('Field Interaction Error!', this.control.key); // tslint:disable-line
console.error(err); // tslint:disable-line
}
});
}
}
ngOnDestroy() {
// Unsubscribe from control interactions
if (this.valueChangeSubscription) {
this.valueChangeSubscription.unsubscribe();
}
// if (this.dateChangeSubscription) {
// this.dateChangeSubscription.unsubscribe();
// }
if (this.forceClearSubscription) {
// Un-listen for clear events
this.forceClearSubscription.unsubscribe();
}
if (this.percentChangeSubscription) {
// Un-listen for clear events
this.percentChangeSubscription.unsubscribe();
}
if (this.dateChangeSubscription) {
this.dateChangeSubscription.unsubscribe();
}
super.ngOnDestroy();
}
get errors() {
return this.form.controls[this.control.key].errors;
}
get isValid() {
return this.form.controls[this.control.key].valid;
}
get isDirty() {
return this.form.controls[this.control.key].dirty || this.control.dirty;
}
get hasValue() {
return !Helpers.isEmpty(this.form.value[this.control.key]);
}
get focused() {
return this._focused;
}
get tooltip() {
return this.form.controls[this.control.key].tooltip;
}
get tooltipPosition() {
if (Helpers.isBlank(this.form.controls[this.control.key].tooltipPosition)) {
return 'right';
}
return this.form.controls[this.control.key].tooltipPosition;
}
get alwaysActive() {
// Controls that have the label active if there is any user entered text in the field
if (this.form.controls[this.control.key].controlType === 'picker' && this._enteredText.length) {
return true;
}
// Controls that always have the label active
return ['tiles', 'checklist', 'checkbox', 'address', 'file', 'editor', 'radio', 'text-area', 'quick-note'].indexOf(this.form.controls[this.control.key].controlType) !== -1;
}
get requiresExtraSpacing() {
// Chips
if (this.form.controls[this.control.key].controlType === 'picker' && this.form.controls[this.control.key].multiple && this.hasValue) {
return true;
}
return false;
}
handleTyping(event: any) {
this._focused = event && event.length;
this._enteredText = event;
}
handleFocus(event: FocusEvent) {
this._focused = true;
this._focusEmitter.emit(event);
}
handleBlur(event: FocusEvent) {
this._focused = false;
this._blurEmitter.emit(event);
}
clearValue() {
this.form.controls[this.control.key].setValue(null);
this.formattedValue = null;
}
handleTextAreaInput(event) {
this.emitChange(event);
this.restrictKeys(event);
}
checkMaxLength(event) {
if (this.control && this.form.controls[this.control.key].maxlength) {
this.characterCount = event.target.value.length;
this.maxLengthMet = event.target.value.length >= this.form.controls[this.control.key].maxlength;
}
}
modelChange(value) {
if (Helpers.isEmpty(value)) {
this._focused = false;
this._enteredText = '';
}
this.change.emit(value);
}
restrictKeys(event) {
const NUMBERS_ONLY = /[0-9\-]/;
const NUMBERS_WITH_DECIMAL = /[0-9\.\-]/;
const UTILITY_KEYS = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'];
let key = event.key;
// Types
if (this.form.controls[this.control.key].subType === 'number' && !(NUMBERS_ONLY.test(key) || UTILITY_KEYS.includes(key))) {
event.preventDefault();
} else if (~['currency', 'float', 'percentage'].indexOf(this.form.controls[this.control.key].subType) && !(NUMBERS_WITH_DECIMAL.test(key) || UTILITY_KEYS.includes(key))) {
event.preventDefault();
}
// Max Length
if (this.form.controls[this.control.key].maxlength && event.target.value.length >= this.form.controls[this.control.key].maxlength) {
event.preventDefault();
}
}
handlePercentChange(event: KeyboardEvent) {
let value = event.target['value'];
let percent = Helpers.isEmpty(value) ? null : Number((value / 100).toFixed(6).replace(/\.?0*$/, ''));
if (!Helpers.isEmpty(percent)) {
this.change.emit(percent);
this.form.controls[this.control.key].setValue(percent);
} else {
this.change.emit(null);
this.form.controls[this.control.key].setValue(null);
}
}
handleTabForPickers(event: any): void {
if (this.active && event && event.keyCode) {
if (event.keyCode === KeyCodes.ESC || event.keyCode === KeyCodes.TAB) {
this.toggleActive(event, false);
}
}
}
emitChange(value) {
this.change.emit(value);
this.checkMaxLength(value);
}
}