UNPKG

ngx-bootstrap

Version:
383 lines 16.3 kB
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