711 lines
20 KiB
TypeScript
711 lines
20 KiB
TypeScript
import {
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
DoCheck,
|
|
EventEmitter,
|
|
forwardRef,
|
|
Input,
|
|
IterableDiffers,
|
|
OnChanges,
|
|
OnDestroy,
|
|
OnInit,
|
|
Output,
|
|
SimpleChanges,
|
|
} from '@angular/core';
|
|
|
|
import {
|
|
AbstractControl,
|
|
ControlValueAccessor,
|
|
UntypedFormBuilder,
|
|
UntypedFormControl,
|
|
NG_VALUE_ACCESSOR,
|
|
Validator,
|
|
} from '@angular/forms';
|
|
|
|
import { takeUntil } from 'rxjs/operators';
|
|
import { MultiSelectSearchFilter } from './search-filter.pipe';
|
|
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts, } from './types';
|
|
import { Subject, Observable } from 'rxjs';
|
|
|
|
const MULTISELECT_VALUE_ACCESSOR: any = {
|
|
provide: NG_VALUE_ACCESSOR,
|
|
useExisting: forwardRef(() => NgxDropdownMultiselectComponent),
|
|
multi: true,
|
|
};
|
|
|
|
// tslint:disable-next-line: no-conflicting-lifecycle
|
|
@Component({
|
|
selector: 'ngx-bootstrap-multiselect',
|
|
templateUrl: './ngx-bootstrap-multiselect.component.html',
|
|
styleUrls: ['./ngx-bootstrap-multiselect.component.css'],
|
|
providers: [MULTISELECT_VALUE_ACCESSOR, MultiSelectSearchFilter],
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
})
|
|
export class NgxDropdownMultiselectComponent implements OnInit,
|
|
OnChanges,
|
|
DoCheck,
|
|
OnDestroy,
|
|
ControlValueAccessor,
|
|
Validator {
|
|
|
|
private localIsVisible = false;
|
|
private workerDocClicked = false;
|
|
|
|
filterControl: UntypedFormControl = this.fb.control('');
|
|
|
|
@Input() options: Array<IMultiSelectOption>;
|
|
@Input() settings: IMultiSelectSettings;
|
|
@Input() texts: IMultiSelectTexts;
|
|
@Input() disabled = false;
|
|
@Input() disabledSelection = false;
|
|
@Input() searchFunction: (str: string) => RegExp = this._escapeRegExp;
|
|
|
|
@Output() selectionLimitReached = new EventEmitter();
|
|
@Output() dropdownClosed = new EventEmitter();
|
|
@Output() dropdownOpened = new EventEmitter();
|
|
@Output() added = new EventEmitter();
|
|
@Output() removed = new EventEmitter();
|
|
@Output() lazyLoad = new EventEmitter();
|
|
@Output() filter: Observable<string> = this.filterControl.valueChanges;
|
|
|
|
get focusBack(): boolean {
|
|
return this.settings.focusBack && this._focusBack;
|
|
}
|
|
|
|
destroyed$ = new Subject<any>();
|
|
|
|
filteredOptions: IMultiSelectOption[] = [];
|
|
lazyLoadOptions: IMultiSelectOption[] = [];
|
|
renderFilteredOptions: IMultiSelectOption[] = [];
|
|
model: any[] = [];
|
|
prevModel: any[] = [];
|
|
parents: any[];
|
|
title: string;
|
|
differ: any;
|
|
numSelected = 0;
|
|
set isVisible(val: boolean) {
|
|
this.localIsVisible = val;
|
|
this.workerDocClicked = val ? false : this.workerDocClicked;
|
|
}
|
|
get isVisible(): boolean {
|
|
return this.localIsVisible;
|
|
}
|
|
renderItems = true;
|
|
checkAllSearchRegister = new Set();
|
|
checkAllStatus = false;
|
|
loadedValueIds = [];
|
|
_focusBack = false;
|
|
focusedItem: IMultiSelectOption | undefined;
|
|
|
|
defaultSettings: IMultiSelectSettings = {
|
|
closeOnClickOutside: true,
|
|
pullRight: false,
|
|
enableSearch: false,
|
|
searchRenderLimit: 0,
|
|
searchRenderAfter: 1,
|
|
searchMaxLimit: 0,
|
|
searchMaxRenderedItems: 0,
|
|
checkedStyle: 'checkboxes',
|
|
buttonClasses: 'btn btn-primary dropdown-toggle',
|
|
containerClasses: 'dropdown-inline',
|
|
selectionLimit: 0,
|
|
minSelectionLimit: 0,
|
|
closeOnSelect: false,
|
|
autoUnselect: false,
|
|
showCheckAll: false,
|
|
showUncheckAll: false,
|
|
fixedTitle: false,
|
|
dynamicTitleMaxItems: 3,
|
|
maxHeight: '300px',
|
|
isLazyLoad: false,
|
|
stopScrollPropagation: false,
|
|
loadViewDistance: 1,
|
|
selectAddedValues: false,
|
|
ignoreLabels: false,
|
|
maintainSelectionOrderInTitle: false,
|
|
focusBack: true
|
|
};
|
|
defaultTexts: IMultiSelectTexts = {
|
|
checkAll: 'Select all',
|
|
uncheckAll: 'Unselect all',
|
|
checked: 'selected',
|
|
checkedPlural: 'selected',
|
|
searchPlaceholder: 'Search...',
|
|
searchEmptyResult: 'Nothing found...',
|
|
searchNoRenderText: 'Type in search box to see results...',
|
|
defaultTitle: 'Select',
|
|
allSelected: 'All selected',
|
|
};
|
|
|
|
get searchLimit(): number | undefined {
|
|
return this.settings.searchRenderLimit;
|
|
}
|
|
|
|
get searchRenderAfter(): number | undefined {
|
|
return this.settings.searchRenderAfter;
|
|
}
|
|
|
|
get searchLimitApplied(): boolean {
|
|
return this.searchLimit > 0 && this.options.length > this.searchLimit;
|
|
}
|
|
|
|
constructor(
|
|
private fb: UntypedFormBuilder,
|
|
private searchFilter: MultiSelectSearchFilter,
|
|
differs: IterableDiffers,
|
|
private cdRef: ChangeDetectorRef
|
|
) {
|
|
this.differ = differs.find([]).create(null);
|
|
this.settings = this.defaultSettings;
|
|
this.texts = this.defaultTexts;
|
|
}
|
|
|
|
clickedOutside(): void {
|
|
if (!this.isVisible || !this.settings.closeOnClickOutside) { return; }
|
|
|
|
this.isVisible = false;
|
|
this._focusBack = true;
|
|
this.dropdownClosed.emit();
|
|
}
|
|
|
|
getItemStyle(option: IMultiSelectOption): any {
|
|
const style = {};
|
|
if (!option.isLabel) {
|
|
style['cursor'] = 'pointer';
|
|
}
|
|
if (option.disabled) {
|
|
style['cursor'] = 'default';
|
|
}
|
|
}
|
|
|
|
getItemStyleSelectionDisabled(): any {
|
|
if (this.disabledSelection) {
|
|
return { cursor: 'default' };
|
|
}
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.title = this.texts.defaultTitle || '';
|
|
|
|
this.filterControl.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
|
|
this.updateRenderItems();
|
|
if (this.settings.isLazyLoad) {
|
|
this.load();
|
|
}
|
|
});
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
if (changes['options']) {
|
|
this.options = this.options || [];
|
|
this.parents = this.options
|
|
.filter(option => typeof option.parentId === 'number')
|
|
.map(option => option.parentId);
|
|
this.updateRenderItems();
|
|
|
|
if (
|
|
this.settings.isLazyLoad &&
|
|
this.settings.selectAddedValues &&
|
|
this.loadedValueIds.length === 0
|
|
) {
|
|
this.loadedValueIds = this.loadedValueIds.concat(
|
|
changes.options.currentValue.map(value => value.id)
|
|
);
|
|
}
|
|
if (
|
|
this.settings.isLazyLoad &&
|
|
this.settings.selectAddedValues &&
|
|
changes.options.previousValue
|
|
) {
|
|
const addedValues = changes.options.currentValue.filter(
|
|
value => this.loadedValueIds.indexOf(value.id) === -1
|
|
);
|
|
this.loadedValueIds.concat(addedValues.map(value => value.id));
|
|
if (this.checkAllStatus) {
|
|
this.addChecks(addedValues);
|
|
} else if (this.checkAllSearchRegister.size > 0) {
|
|
this.checkAllSearchRegister.forEach((searchValue: string) =>
|
|
this.addChecks(this.applyFilters(addedValues, searchValue))
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this.texts) {
|
|
this.updateTitle();
|
|
}
|
|
|
|
this.fireModelChange();
|
|
}
|
|
|
|
if (changes['settings']) {
|
|
this.settings = { ...this.defaultSettings, ...this.settings };
|
|
}
|
|
|
|
if (changes['texts']) {
|
|
this.texts = { ...this.defaultTexts, ...this.texts };
|
|
if (!changes['texts'].isFirstChange()) { this.updateTitle(); }
|
|
}
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.destroyed$.next(false);
|
|
}
|
|
|
|
updateRenderItems() {
|
|
this.renderItems =
|
|
!this.searchLimitApplied ||
|
|
this.filterControl.value.length >= this.searchRenderAfter;
|
|
this.filteredOptions = this.applyFilters(
|
|
this.options,
|
|
this.settings.isLazyLoad ? '' : this.filterControl.value
|
|
);
|
|
this.renderFilteredOptions = this.renderItems ? this.filteredOptions : [];
|
|
this.focusedItem = undefined;
|
|
}
|
|
|
|
applyFilters(options: IMultiSelectOption[], value: string): IMultiSelectOption[] {
|
|
return this.searchFilter.transform(
|
|
options,
|
|
value,
|
|
this.settings.searchMaxLimit,
|
|
this.settings.searchMaxRenderedItems,
|
|
this.searchFunction
|
|
);
|
|
}
|
|
|
|
fireModelChange(): void {
|
|
if (this.model != this.prevModel) {
|
|
this.prevModel = this.model;
|
|
this.onModelChange(this.model);
|
|
this.onModelTouched();
|
|
this.cdRef.markForCheck();
|
|
}
|
|
}
|
|
|
|
onModelChange: Function = (_: any) => { };
|
|
onModelTouched: Function = () => { };
|
|
|
|
writeValue(value: any): void {
|
|
if (value !== undefined && value !== null) {
|
|
this.model = Array.isArray(value) ? value : [value];
|
|
this.ngDoCheck();
|
|
} else {
|
|
this.model = [];
|
|
}
|
|
}
|
|
|
|
registerOnChange(fn: Function): void {
|
|
this.onModelChange = fn;
|
|
}
|
|
|
|
registerOnTouched(fn: Function): void {
|
|
this.onModelTouched = fn;
|
|
}
|
|
|
|
setDisabledState(isDisabled: boolean) {
|
|
this.disabled = isDisabled;
|
|
}
|
|
|
|
ngDoCheck() {
|
|
const changes = this.differ.diff(this.model);
|
|
if (changes) {
|
|
this.updateNumSelected();
|
|
this.updateTitle();
|
|
}
|
|
}
|
|
|
|
validate(_c: AbstractControl): { [key: string]: any } {
|
|
if (this.model && this.model.length) {
|
|
return {
|
|
required: {
|
|
valid: false
|
|
}
|
|
};
|
|
}
|
|
|
|
if (this.options.filter(o => this.model.indexOf(o.id) && !o.disabled).length === 0) {
|
|
return {
|
|
selection: {
|
|
valid: false
|
|
}
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
registerOnValidatorChange(_fn: () => void): void {
|
|
throw new Error('Method not implemented.');
|
|
}
|
|
|
|
clearSearch(event: Event) {
|
|
this.maybeStopPropagation(event);
|
|
this.filterControl.setValue('');
|
|
}
|
|
|
|
toggleDropdown(e?: Event) {
|
|
if (this.isVisible) {
|
|
this._focusBack = true;
|
|
}
|
|
|
|
this.isVisible = !this.isVisible;
|
|
this.isVisible ? this.dropdownOpened.emit() : this.dropdownClosed.emit();
|
|
this.focusedItem = undefined;
|
|
}
|
|
|
|
closeDropdown(e?: Event) {
|
|
this.isVisible = true;
|
|
this.toggleDropdown(e);
|
|
}
|
|
|
|
isSelected(option: IMultiSelectOption): boolean {
|
|
return this.model && this.model.indexOf(option.id) > -1;
|
|
}
|
|
|
|
setSelected(_event: Event, option: IMultiSelectOption) {
|
|
if (option.isLabel) {
|
|
return;
|
|
}
|
|
|
|
if (option.disabled) {
|
|
return;
|
|
}
|
|
|
|
if (this.disabledSelection) {
|
|
return;
|
|
}
|
|
|
|
setTimeout(() => {
|
|
this.maybeStopPropagation(_event);
|
|
this.maybePreventDefault(_event);
|
|
const index = this.model.indexOf(option.id);
|
|
const isAtSelectionLimit =
|
|
this.settings.selectionLimit > 0 &&
|
|
this.model.length >= this.settings.selectionLimit;
|
|
const removeItem = (idx, id): void => {
|
|
this.model.splice(idx, 1);
|
|
this.removed.emit(id);
|
|
if (
|
|
this.settings.isLazyLoad &&
|
|
this.lazyLoadOptions.some(val => val.id === id)
|
|
) {
|
|
this.lazyLoadOptions.splice(
|
|
this.lazyLoadOptions.indexOf(
|
|
this.lazyLoadOptions.find(val => val.id === id)
|
|
),
|
|
1
|
|
);
|
|
}
|
|
};
|
|
|
|
if (index > -1) {
|
|
if (
|
|
this.settings.minSelectionLimit === undefined ||
|
|
this.numSelected > this.settings.minSelectionLimit
|
|
) {
|
|
removeItem(index, option.id);
|
|
}
|
|
const parentIndex =
|
|
option.parentId && this.model.indexOf(option.parentId);
|
|
if (parentIndex > -1) {
|
|
removeItem(parentIndex, option.parentId);
|
|
} else if (this.parents.indexOf(option.id) > -1) {
|
|
this.options
|
|
.filter(
|
|
child =>
|
|
this.model.indexOf(child.id) > -1 &&
|
|
child.parentId === option.id
|
|
)
|
|
.forEach(child =>
|
|
removeItem(this.model.indexOf(child.id), child.id)
|
|
);
|
|
}
|
|
} else if (isAtSelectionLimit && !this.settings.autoUnselect) {
|
|
this.selectionLimitReached.emit(this.model.length);
|
|
return;
|
|
} else {
|
|
const addItem = (id): void => {
|
|
this.model.push(id);
|
|
this.added.emit(id);
|
|
if (
|
|
this.settings.isLazyLoad &&
|
|
!this.lazyLoadOptions.some(val => val.id === id)
|
|
) {
|
|
this.lazyLoadOptions.push(option);
|
|
}
|
|
};
|
|
|
|
addItem(option.id);
|
|
if (!isAtSelectionLimit) {
|
|
if (option.parentId && !this.settings.ignoreLabels) {
|
|
const children = this.options.filter(
|
|
child =>
|
|
child.id !== option.id && child.parentId === option.parentId
|
|
);
|
|
if (children.every(child => this.model.indexOf(child.id) > -1)) {
|
|
addItem(option.parentId);
|
|
}
|
|
} else if (this.parents.indexOf(option.id) > -1) {
|
|
const children = this.options.filter(
|
|
child =>
|
|
this.model.indexOf(child.id) < 0 && child.parentId === option.id
|
|
);
|
|
children.forEach(child => addItem(child.id));
|
|
}
|
|
} else {
|
|
removeItem(0, this.model[0]);
|
|
}
|
|
}
|
|
if (this.settings.closeOnSelect) {
|
|
this.toggleDropdown();
|
|
}
|
|
this.model = this.model.slice();
|
|
this.fireModelChange();
|
|
|
|
}, 0)
|
|
}
|
|
|
|
updateNumSelected() {
|
|
this.numSelected =
|
|
this.model.filter(id => this.parents.indexOf(id) < 0).length || 0;
|
|
}
|
|
|
|
updateTitle() {
|
|
let numSelectedOptions = this.options.length;
|
|
if (this.settings.ignoreLabels) {
|
|
numSelectedOptions = this.options.filter(
|
|
(option: IMultiSelectOption) => !option.isLabel
|
|
).length;
|
|
}
|
|
if (this.numSelected === 0 || this.settings.fixedTitle) {
|
|
this.title = this.texts ? this.texts.defaultTitle : '';
|
|
} else if (
|
|
this.settings.displayAllSelectedText &&
|
|
this.model.length === numSelectedOptions
|
|
) {
|
|
this.title = this.texts ? this.texts.allSelected : '';
|
|
} else if (
|
|
this.settings.dynamicTitleMaxItems &&
|
|
this.settings.dynamicTitleMaxItems >= this.numSelected
|
|
) {
|
|
const useOptions =
|
|
this.settings.isLazyLoad && this.lazyLoadOptions.length
|
|
? this.lazyLoadOptions
|
|
: this.options;
|
|
|
|
let titleSelections: Array<IMultiSelectOption>;
|
|
|
|
if (this.settings.maintainSelectionOrderInTitle) {
|
|
const optionIds = useOptions.map((selectOption: IMultiSelectOption, idx: number) => selectOption.id);
|
|
titleSelections = this.model
|
|
.map((selectedId) => optionIds.indexOf(selectedId))
|
|
.filter((optionIndex) => optionIndex > -1)
|
|
.map((optionIndex) => useOptions[optionIndex]);
|
|
} else {
|
|
titleSelections = useOptions.filter((option: IMultiSelectOption) => this.model.indexOf(option.id) > -1);
|
|
}
|
|
|
|
this.title = titleSelections.map((option: IMultiSelectOption) => option.name).join(', ');
|
|
} else {
|
|
this.title =
|
|
this.numSelected +
|
|
' ' +
|
|
(this.numSelected === 1
|
|
? this.texts.checked
|
|
: this.texts.checkedPlural);
|
|
}
|
|
this.cdRef.markForCheck();
|
|
}
|
|
|
|
searchFilterApplied() {
|
|
return (
|
|
this.settings.enableSearch &&
|
|
this.filterControl.value &&
|
|
this.filterControl.value.length > 0
|
|
);
|
|
}
|
|
|
|
addChecks(options) {
|
|
const checkedOptions = options
|
|
.filter((option: IMultiSelectOption) => {
|
|
if (
|
|
!option.disabled &&
|
|
(
|
|
this.model.indexOf(option.id) === -1 &&
|
|
!(this.settings.ignoreLabels && option.isLabel)
|
|
)
|
|
) {
|
|
this.added.emit(option.id);
|
|
return true;
|
|
}
|
|
return false;
|
|
})
|
|
.map((option: IMultiSelectOption) => option.id);
|
|
|
|
this.model = this.model.concat(checkedOptions);
|
|
}
|
|
|
|
checkAll(): void {
|
|
if (!this.disabledSelection) {
|
|
this.addChecks(
|
|
!this.searchFilterApplied() ? this.options : this.filteredOptions
|
|
);
|
|
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
|
|
if (this.searchFilterApplied() && !this.checkAllStatus) {
|
|
this.checkAllSearchRegister.add(this.filterControl.value);
|
|
} else {
|
|
this.checkAllSearchRegister.clear();
|
|
this.checkAllStatus = true;
|
|
}
|
|
this.load();
|
|
}
|
|
this.fireModelChange();
|
|
}
|
|
}
|
|
|
|
uncheckAll(): void {
|
|
if (!this.disabledSelection) {
|
|
const checkedOptions = this.model;
|
|
let unCheckedOptions = !this.searchFilterApplied()
|
|
? this.model
|
|
: this.filteredOptions.map((option: IMultiSelectOption) => option.id);
|
|
// set unchecked options only to the ones that were checked
|
|
unCheckedOptions = checkedOptions.filter(item => unCheckedOptions.indexOf(item) > -1);
|
|
this.model = this.model.filter((id: number) => {
|
|
if (
|
|
(unCheckedOptions.indexOf(id) < 0 &&
|
|
this.settings.minSelectionLimit === undefined) ||
|
|
unCheckedOptions.indexOf(id) < this.settings.minSelectionLimit
|
|
) {
|
|
return true;
|
|
} else {
|
|
this.removed.emit(id);
|
|
return false;
|
|
}
|
|
});
|
|
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
|
|
if (this.searchFilterApplied()) {
|
|
if (this.checkAllSearchRegister.has(this.filterControl.value)) {
|
|
this.checkAllSearchRegister.delete(this.filterControl.value);
|
|
this.checkAllSearchRegister.forEach(function(searchTerm) {
|
|
const filterOptions = this.applyFilters(this.options.filter(option => unCheckedOptions.indexOf(option.id) > -1), searchTerm);
|
|
this.addChecks(filterOptions);
|
|
});
|
|
}
|
|
} else {
|
|
this.checkAllSearchRegister.clear();
|
|
this.checkAllStatus = false;
|
|
}
|
|
this.load();
|
|
}
|
|
this.fireModelChange();
|
|
}
|
|
}
|
|
|
|
preventCheckboxCheck(event: Event, option: IMultiSelectOption): void {
|
|
if (
|
|
option.disabled ||
|
|
(
|
|
this.settings.selectionLimit &&
|
|
!this.settings.autoUnselect &&
|
|
this.model.length >= this.settings.selectionLimit &&
|
|
this.model.indexOf(option.id) === -1 &&
|
|
this.maybePreventDefault(event)
|
|
)
|
|
) {
|
|
this.maybePreventDefault(event);
|
|
}
|
|
}
|
|
|
|
isCheckboxDisabled(option?: IMultiSelectOption): boolean {
|
|
return this.disabledSelection || option && option.disabled;
|
|
}
|
|
|
|
checkScrollPosition(ev): void {
|
|
const scrollTop = ev.target.scrollTop;
|
|
const scrollHeight = ev.target.scrollHeight;
|
|
const scrollElementHeight = ev.target.clientHeight;
|
|
const roundingPixel = 1;
|
|
const gutterPixel = 1;
|
|
|
|
if (
|
|
scrollTop >=
|
|
scrollHeight -
|
|
(1 + this.settings.loadViewDistance) * scrollElementHeight -
|
|
roundingPixel -
|
|
gutterPixel
|
|
) {
|
|
this.load();
|
|
}
|
|
}
|
|
|
|
checkScrollPropagation(ev, element): void {
|
|
const scrollTop = element.scrollTop;
|
|
const scrollHeight = element.scrollHeight;
|
|
const scrollElementHeight = element.clientHeight;
|
|
|
|
if (
|
|
(ev.deltaY > 0 && scrollTop + scrollElementHeight >= scrollHeight) ||
|
|
(ev.deltaY < 0 && scrollTop <= 0)
|
|
) {
|
|
ev = ev || window.event;
|
|
this.maybePreventDefault(ev);
|
|
ev.returnValue = false;
|
|
}
|
|
}
|
|
|
|
trackById(idx: number, selectOption: IMultiSelectOption): void {
|
|
return selectOption.id;
|
|
}
|
|
|
|
load(): void {
|
|
this.lazyLoad.emit({
|
|
length: this.options.length,
|
|
filter: this.filterControl.value,
|
|
checkAllSearches: this.checkAllSearchRegister,
|
|
checkAllStatus: this.checkAllStatus,
|
|
});
|
|
}
|
|
|
|
focusItem(dir: number, e?: Event): void {
|
|
if (!this.isVisible) {
|
|
return;
|
|
}
|
|
|
|
this.maybePreventDefault(e);
|
|
|
|
const idx = this.filteredOptions.indexOf(this.focusedItem);
|
|
|
|
if (idx === -1) {
|
|
this.focusedItem = this.filteredOptions[0];
|
|
return;
|
|
}
|
|
|
|
const nextIdx = idx + dir;
|
|
const newIdx =
|
|
nextIdx < 0
|
|
? this.filteredOptions.length - 1
|
|
: nextIdx % this.filteredOptions.length;
|
|
|
|
this.focusedItem = this.filteredOptions[newIdx];
|
|
}
|
|
|
|
private maybePreventDefault(e?: Event): void {
|
|
if (e && e.preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
private maybeStopPropagation(e?: Event): void {
|
|
if (e && e.stopPropagation) {
|
|
e.stopPropagation();
|
|
}
|
|
}
|
|
|
|
private _escapeRegExp(str: string): RegExp {
|
|
const regExpStr = str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
|
|
return new RegExp(regExpStr, 'i');
|
|
}
|
|
}
|