ngx-bootstrap
Version:
Native Angular Bootstrap Components
383 lines • 16.3 kB
JavaScript
import { ChangeDetectorRef, Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer2, TemplateRef, ViewContainerRef } from '@angular/core';
import { NgControl } from '@angular/forms';
import { from, Observable } from 'rxjs';
import { ComponentLoaderFactory } from '../component-loader/index';
import { TypeaheadContainerComponent } from './typeahead-container.component';
import { TypeaheadMatch } from './typeahead-match.class';
import { getValueFromObject, latinize, tokenize } from './typeahead-utils';
import { debounceTime, filter, mergeMap, switchMap, toArray } from 'rxjs/operators';
var TypeaheadDirective = /** @class */ (function () {
function TypeaheadDirective(ngControl, element, viewContainerRef, renderer, cis, changeDetection) {
this.ngControl = ngControl;
this.element = element;
this.renderer = renderer;
this.changeDetection = changeDetection;
/** minimal no of characters that needs to be entered before
* typeahead kicks-in. When set to 0, typeahead shows on focus with full
* list of options (limited as normal by typeaheadOptionsLimit)
*/
this.typeaheadMinLength = void 0;
/** should be used only in case of typeahead attribute is array.
* If true - loading of options will be async, otherwise - sync.
* true make sense if options array is large.
*/
this.typeaheadAsync = void 0;
/** match latin symbols.
* If true the word súper would match super and vice versa.
*/
this.typeaheadLatinize = true;
/** Can be use to search words by inserting a single white space between each characters
* for example 'C a l i f o r n i a' will match 'California'.
*/
this.typeaheadSingleWords = true;
/** should be used only in case typeaheadSingleWords attribute is true.
* Sets the word delimiter to break words. Defaults to space.
*/
this.typeaheadWordDelimiters = ' ';
/** should be used only in case typeaheadSingleWords attribute is true.
* Sets the word delimiter to match exact phrase.
* Defaults to simple and double quotes.
*/
this.typeaheadPhraseDelimiters = '\'"';
/** specifies if typeahead is scrollable */
this.typeaheadScrollable = false;
/** specifies number of options to show in scroll view */
this.typeaheadOptionsInScrollableView = 5;
/** fired when 'busy' state of this component was changed,
* fired on async mode only, returns boolean
*/
this.typeaheadLoading = new EventEmitter();
/** fired on every key event and returns true
* in case of matches are not detected
*/
this.typeaheadNoResults = new EventEmitter();
/** fired when option was selected, return object with data of this option */
this.typeaheadOnSelect = new EventEmitter();
/** fired when blur event occurres. returns the active item */
this.typeaheadOnBlur = new EventEmitter();
/** This attribute indicates that the dropdown should be opened upwards */
this.dropup = false;
this.isTypeaheadOptionsListActive = false;
this.keyUpEventEmitter = new EventEmitter();
this.placement = 'bottom-left';
this._subscriptions = [];
this._typeahead = cis.createLoader(element, viewContainerRef, renderer);
}
TypeaheadDirective.prototype.ngOnInit = function () {
this.typeaheadOptionsLimit = this.typeaheadOptionsLimit || 20;
this.typeaheadMinLength =
this.typeaheadMinLength === void 0 ? 1 : this.typeaheadMinLength;
this.typeaheadWaitMs = this.typeaheadWaitMs || 0;
// async should be false in case of array
if (this.typeaheadAsync === undefined &&
!(this.typeahead instanceof Observable)) {
this.typeaheadAsync = false;
}
if (this.typeahead instanceof Observable) {
this.typeaheadAsync = true;
}
if (this.typeaheadAsync) {
this.asyncActions();
}
else {
this.syncActions();
}
};
TypeaheadDirective.prototype.onInput = function (e) {
// For `<input>`s, use the `value` property. For others that don't have a
// `value` (such as `<span contenteditable="true">`), use either
// `textContent` or `innerText` (depending on which one is supported, i.e.
// Firefox or IE).
var value = e.target.value !== undefined
? e.target.value
: e.target.textContent !== undefined
? e.target.textContent
: e.target.innerText;
if (value != null && value.trim().length >= this.typeaheadMinLength) {
this.typeaheadLoading.emit(true);
this.keyUpEventEmitter.emit(e.target.value);
}
else {
this.typeaheadLoading.emit(false);
this.typeaheadNoResults.emit(false);
this.hide();
}
};
TypeaheadDirective.prototype.onChange = function (e) {
if (this._container) {
// esc
if (e.keyCode === 27) {
this.hide();
return;
}
// up
if (e.keyCode === 38) {
this._container.prevActiveMatch();
return;
}
// down
if (e.keyCode === 40) {
this._container.nextActiveMatch();
return;
}
// enter, tab
if (e.keyCode === 13) {
this._container.selectActiveMatch();
return;
}
}
};
TypeaheadDirective.prototype.onFocus = function () {
if (this.typeaheadMinLength === 0) {
this.typeaheadLoading.emit(true);
this.keyUpEventEmitter.emit(this.element.nativeElement.value || '');
}
};
TypeaheadDirective.prototype.onBlur = function () {
if (this._container && !this._container.isFocused) {
this.typeaheadOnBlur.emit(this._container.active);
}
};
TypeaheadDirective.prototype.onKeydown = function (e) {
// no container - no problems
if (!this._container) {
return;
}
// if an item is visible - prevent form submission
if (e.keyCode === 13) {
e.preventDefault();
return;
}
// if an item is visible - don't change focus
if (e.keyCode === 9) {
e.preventDefault();
this._container.selectActiveMatch();
return;
}
};
TypeaheadDirective.prototype.changeModel = function (match) {
var valueStr = match.value;
this.ngControl.viewToModelUpdate(valueStr);
(this.ngControl.control).setValue(valueStr);
this.changeDetection.markForCheck();
this.hide();
};
Object.defineProperty(TypeaheadDirective.prototype, "matches", {
get: function () {
return this._matches;
},
enumerable: true,
configurable: true
});
TypeaheadDirective.prototype.show = function () {
var _this = this;
this._typeahead
.attach(TypeaheadContainerComponent)
.to(this.container)
.position({ attachment: (this.dropup ? 'top' : 'bottom') + " left" })
.show({
typeaheadRef: this,
placement: this.placement,
animation: false,
dropup: this.dropup
});
this._outsideClickListener = this.renderer.listen('document', 'click', function (e) {
if (_this.typeaheadMinLength === 0 && _this.element.nativeElement.contains(e.target)) {
return;
}
_this.onOutsideClick();
});
this._container = this._typeahead.instance;
this._container.parent = this;
// This improves the speed as it won't have to be done for each list item
var normalizedQuery = (this.typeaheadLatinize
? latinize(this.ngControl.control.value)
: this.ngControl.control.value)
.toString()
.toLowerCase();
this._container.query = this.typeaheadSingleWords
? tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters)
: normalizedQuery;
this._container.matches = this._matches;
this.element.nativeElement.focus();
};
TypeaheadDirective.prototype.hide = function () {
if (this._typeahead.isShown) {
this._typeahead.hide();
this._outsideClickListener();
this._container = null;
}
};
TypeaheadDirective.prototype.onOutsideClick = function () {
if (this._container && !this._container.isFocused) {
this.hide();
}
};
TypeaheadDirective.prototype.ngOnDestroy = function () {
// clean up subscriptions
for (var _i = 0, _a = this._subscriptions; _i < _a.length; _i++) {
var sub = _a[_i];
sub.unsubscribe();
}
this._typeahead.dispose();
};
TypeaheadDirective.prototype.asyncActions = function () {
var _this = this;
this._subscriptions.push(this.keyUpEventEmitter
.pipe(debounceTime(this.typeaheadWaitMs), switchMap(function () { return _this.typeahead; }))
.subscribe(function (matches) {
_this.finalizeAsyncCall(matches);
}));
};
TypeaheadDirective.prototype.syncActions = function () {
var _this = this;
this._subscriptions.push(this.keyUpEventEmitter
.pipe(debounceTime(this.typeaheadWaitMs), mergeMap(function (value) {
var normalizedQuery = _this.normalizeQuery(value);
return from(_this.typeahead)
.pipe(filter(function (option) {
return (option &&
_this.testMatch(_this.normalizeOption(option), normalizedQuery));
}), toArray());
}))
.subscribe(function (matches) {
_this.finalizeAsyncCall(matches);
}));
};
TypeaheadDirective.prototype.normalizeOption = function (option) {
var optionValue = getValueFromObject(option, this.typeaheadOptionField);
var normalizedOption = this.typeaheadLatinize
? latinize(optionValue)
: optionValue;
return normalizedOption.toLowerCase();
};
TypeaheadDirective.prototype.normalizeQuery = function (value) {
// If singleWords, break model here to not be doing extra work on each
// iteration
var normalizedQuery = (this.typeaheadLatinize
? latinize(value)
: value)
.toString()
.toLowerCase();
normalizedQuery = this.typeaheadSingleWords
? tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters)
: normalizedQuery;
return normalizedQuery;
};
TypeaheadDirective.prototype.testMatch = function (match, test) {
var spaceLength;
if (typeof test === 'object') {
spaceLength = test.length;
for (var i = 0; i < spaceLength; i += 1) {
if (test[i].length > 0 && match.indexOf(test[i]) < 0) {
return false;
}
}
return true;
}
return match.indexOf(test) >= 0;
};
TypeaheadDirective.prototype.finalizeAsyncCall = function (matches) {
this.prepareMatches(matches);
this.typeaheadLoading.emit(false);
this.typeaheadNoResults.emit(!this.hasMatches());
if (!this.hasMatches()) {
this.hide();
return;
}
if (this._container) {
// This improves the speed as it won't have to be done for each list item
var normalizedQuery = (this.typeaheadLatinize
? latinize(this.ngControl.control.value)
: this.ngControl.control.value)
.toString()
.toLowerCase();
this._container.query = this.typeaheadSingleWords
? tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters)
: normalizedQuery;
this._container.matches = this._matches;
}
else {
this.show();
}
};
TypeaheadDirective.prototype.prepareMatches = function (options) {
var _this = this;
var limited = options.slice(0, this.typeaheadOptionsLimit);
if (this.typeaheadGroupField) {
var matches_1 = [];
// extract all group names
var groups = limited
.map(function (option) {
return getValueFromObject(option, _this.typeaheadGroupField);
})
.filter(function (v, i, a) { return a.indexOf(v) === i; });
groups.forEach(function (group) {
// add group header to array of matches
// add group header to array of matches
matches_1.push(new TypeaheadMatch(group, group, true));
// add each item of group to array of matches
// add each item of group to array of matches
matches_1 = matches_1.concat(limited
.filter(function (option) {
return getValueFromObject(option, _this.typeaheadGroupField) === group;
})
.map(function (option) {
return new TypeaheadMatch(option, getValueFromObject(option, _this.typeaheadOptionField));
}));
});
this._matches = matches_1;
}
else {
this._matches = limited.map(function (option) {
return new TypeaheadMatch(option, getValueFromObject(option, _this.typeaheadOptionField));
});
}
};
TypeaheadDirective.prototype.hasMatches = function () {
return this._matches.length > 0;
};
TypeaheadDirective.decorators = [
{ type: Directive, args: [{ selector: '[typeahead]', exportAs: 'bs-typeahead' },] },
];
/** @nocollapse */
TypeaheadDirective.ctorParameters = function () { return [
{ type: NgControl, },
{ type: ElementRef, },
{ type: ViewContainerRef, },
{ type: Renderer2, },
{ type: ComponentLoaderFactory, },
{ type: ChangeDetectorRef, },
]; };
TypeaheadDirective.propDecorators = {
"typeahead": [{ type: Input },],
"typeaheadMinLength": [{ type: Input },],
"typeaheadWaitMs": [{ type: Input },],
"typeaheadOptionsLimit": [{ type: Input },],
"typeaheadOptionField": [{ type: Input },],
"typeaheadGroupField": [{ type: Input },],
"typeaheadAsync": [{ type: Input },],
"typeaheadLatinize": [{ type: Input },],
"typeaheadSingleWords": [{ type: Input },],
"typeaheadWordDelimiters": [{ type: Input },],
"typeaheadPhraseDelimiters": [{ type: Input },],
"typeaheadItemTemplate": [{ type: Input },],
"optionsListTemplate": [{ type: Input },],
"typeaheadScrollable": [{ type: Input },],
"typeaheadOptionsInScrollableView": [{ type: Input },],
"typeaheadLoading": [{ type: Output },],
"typeaheadNoResults": [{ type: Output },],
"typeaheadOnSelect": [{ type: Output },],
"typeaheadOnBlur": [{ type: Output },],
"container": [{ type: Input },],
"dropup": [{ type: Input },],
"onInput": [{ type: HostListener, args: ['input', ['$event'],] },],
"onChange": [{ type: HostListener, args: ['keyup', ['$event'],] },],
"onFocus": [{ type: HostListener, args: ['click',] }, { type: HostListener, args: ['focus',] },],
"onBlur": [{ type: HostListener, args: ['blur',] },],
"onKeydown": [{ type: HostListener, args: ['keydown', ['$event'],] },],
};
return TypeaheadDirective;
}());
export { TypeaheadDirective };
//# sourceMappingURL=typeahead.directive.js.map