Moving ngx-bootrap-multiselect to the project
This commit is contained in:
		
							parent
							
								
									b8c49a693d
								
							
						
					
					
						commit
						d8434c59f4
					
				
							
								
								
									
										22
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -36,7 +36,6 @@
 | 
			
		||||
        "echarts": "~5.3.2",
 | 
			
		||||
        "express": "^4.17.1",
 | 
			
		||||
        "lightweight-charts": "~3.8.0",
 | 
			
		||||
        "ngx-bootrap-multiselect": "^2.0.0",
 | 
			
		||||
        "ngx-echarts": "8.0.1",
 | 
			
		||||
        "ngx-infinite-scroll": "^10.0.1",
 | 
			
		||||
        "qrcode": "1.5.0",
 | 
			
		||||
@ -12788,19 +12787,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ngx-bootrap-multiselect": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@angular/common": "^10.0.6",
 | 
			
		||||
        "@angular/core": "^10.0.6",
 | 
			
		||||
        "@angular/forms": "^10.0.6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ngx-echarts": {
 | 
			
		||||
      "version": "8.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
 | 
			
		||||
@ -27418,14 +27404,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
 | 
			
		||||
    },
 | 
			
		||||
    "ngx-bootrap-multiselect": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ngx-echarts": {
 | 
			
		||||
      "version": "8.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
 | 
			
		||||
 | 
			
		||||
@ -90,7 +90,6 @@
 | 
			
		||||
    "echarts": "~5.3.2",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "lightweight-charts": "~3.8.0",
 | 
			
		||||
    "ngx-bootrap-multiselect": "^2.0.0",
 | 
			
		||||
    "ngx-echarts": "8.0.1",
 | 
			
		||||
    "ngx-infinite-scroll": "^10.0.1",
 | 
			
		||||
    "qrcode": "1.5.0",
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
 | 
			
		||||
  <div class="d-block float-right" id="filter">
 | 
			
		||||
    <form [formGroup]="radioGroupForm">
 | 
			
		||||
      <ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
 | 
			
		||||
      <ngx-bootstrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootstrap-multiselect>
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import { BisqApiService } from '../bisq-api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { FormGroup, FormBuilder } from '@angular/forms';
 | 
			
		||||
import { Router, ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
 | 
			
		||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types'
 | 
			
		||||
import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { BisqRoutingModule } from './bisq.routing.module';
 | 
			
		||||
import { SharedModule } from '../shared/shared.module';
 | 
			
		||||
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
 | 
			
		||||
 | 
			
		||||
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
 | 
			
		||||
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
 | 
			
		||||
@ -24,6 +23,10 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
 | 
			
		||||
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
 | 
			
		||||
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
 | 
			
		||||
import { CommonModule } from '@angular/common';
 | 
			
		||||
import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/autofocus.directive';
 | 
			
		||||
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
 | 
			
		||||
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
 | 
			
		||||
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -44,16 +47,21 @@ import { CommonModule } from '@angular/common';
 | 
			
		||||
    BisqMarketComponent,
 | 
			
		||||
    BisqTradesComponent,
 | 
			
		||||
    BisqMainDashboardComponent,
 | 
			
		||||
    NgxDropdownMultiselectComponent,
 | 
			
		||||
    AutofocusDirective,
 | 
			
		||||
    OffClickDirective,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    BisqRoutingModule,
 | 
			
		||||
    SharedModule,
 | 
			
		||||
    FontAwesomeModule,
 | 
			
		||||
    NgxBootstrapMultiselectModule,
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    BisqApiService,
 | 
			
		||||
    MultiSelectSearchFilter,
 | 
			
		||||
    AutofocusDirective,
 | 
			
		||||
    OffClickDirective,
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class BisqModule {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
import { Directive, ElementRef, Host, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Directive({
 | 
			
		||||
  selector: '[ssAutofocus]'
 | 
			
		||||
})
 | 
			
		||||
export class AutofocusDirective implements OnInit, OnChanges {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Will set focus if set to falsy value or not set at all
 | 
			
		||||
   */
 | 
			
		||||
  @Input() ssAutofocus: any;
 | 
			
		||||
 | 
			
		||||
  get element(): { focus?: Function } {
 | 
			
		||||
    return this.elemRef.nativeElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Host() private elemRef: ElementRef,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.focus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes: SimpleChanges) {
 | 
			
		||||
    const ssAutofocusChange = changes.ssAutofocus;
 | 
			
		||||
 | 
			
		||||
    if (ssAutofocusChange && !ssAutofocusChange.isFirstChange()) {
 | 
			
		||||
      this.focus();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  focus() {
 | 
			
		||||
    if (this.ssAutofocus) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.element.focus && this.element.focus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
a {
 | 
			
		||||
  outline: none !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-inline {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-toggle .caret {
 | 
			
		||||
  margin-left: 4px;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chunkydropdown-menu {
 | 
			
		||||
  min-width: 20em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chunkyrow {
 | 
			
		||||
  line-height: 2;
 | 
			
		||||
  margin-left: 1em;
 | 
			
		||||
  font-size: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.slider {
 | 
			
		||||
  width:3.8em;
 | 
			
		||||
  height:3.8em;
 | 
			
		||||
  display:block;
 | 
			
		||||
  -webkit-transition: all 0.125s linear;
 | 
			
		||||
  -moz-transition: all 0.125s linear;
 | 
			
		||||
  -o-transition: all 0.125s linear;
 | 
			
		||||
  transition: all 0.125s linear;
 | 
			
		||||
  margin-left: 0.125em;
 | 
			
		||||
  margin-top: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.slideron {
 | 
			
		||||
  margin-left: 1.35em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content_wrapper{
 | 
			
		||||
  display: table-cell;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-container {
 | 
			
		||||
  padding: 0px 5px 5px 5px;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,72 @@
 | 
			
		||||
<div *ngIf="options" class="dropdown" [ngClass]="settings.containerClasses" [class.open]="isVisible" (offClick)="clickedOutside()">
 | 
			
		||||
  <button type="button" class="dropdown-toggle" [ngClass]="settings.buttonClasses" (click)="toggleDropdown($event)" [disabled]="disabled"
 | 
			
		||||
    [ssAutofocus]="!focusBack">
 | 
			
		||||
    {{ title }}
 | 
			
		||||
    <span class="caret"></span>
 | 
			
		||||
  </button>
 | 
			
		||||
  <div #scroller *ngIf="isVisible" class="dropdown-menu" [ngClass]="{'chunkydropdown-menu': settings.checkedStyle == 'visual' }"
 | 
			
		||||
    (scroll)="settings.isLazyLoad ? checkScrollPosition($event) : null" (wheel)="settings.stopScrollPropagation ? checkScrollPropagation($event, scroller) : null"
 | 
			
		||||
    [class.pull-right]="settings.pullRight" [class.dropdown-menu-right]="settings.pullRight" [style.max-height]="settings.maxHeight"
 | 
			
		||||
    style="display: block; height: auto; overflow-y: auto;" (keydown.tab)="focusItem(1, $event)" (keydown.shift.tab)="focusItem(-1, $event)">
 | 
			
		||||
    <div class="input-group search-container" *ngIf="settings.enableSearch && (renderFilteredOptions.length > 1 || filterControl.value.length > 0)">
 | 
			
		||||
      <div class="input-group-prepend">
 | 
			
		||||
        <span class="input-group-text" id="basic-addon1">
 | 
			
		||||
          <i class="fa fa-search" aria-hidden="true"></i>
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input type="text" class="form-control" ssAutofocus [formControl]="filterControl" [placeholder]="texts.searchPlaceholder"
 | 
			
		||||
        class="form-control">
 | 
			
		||||
      <div class="input-group-append" *ngIf="filterControl.value.length>0">
 | 
			
		||||
        <button class="btn btn-default btn-secondary" type="button" (click)="clearSearch($event)">
 | 
			
		||||
          <i class="fa fa-times"></i>
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-check" *ngIf="settings.showCheckAll && !disabledSelection && renderFilteredOptions.length > 1"
 | 
			
		||||
      (click)="checkAll()">
 | 
			
		||||
      <span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-ok': settings.checkedStyle !== 'fontawesome','fa fa-check': settings.checkedStyle === 'fontawesome'}"></span></span>
 | 
			
		||||
      {{ texts.checkAll }}
 | 
			
		||||
    </a>
 | 
			
		||||
    <a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-uncheck" *ngIf="settings.showUncheckAll && !disabledSelection && renderFilteredOptions.length > 1"
 | 
			
		||||
      (click)="uncheckAll()">
 | 
			
		||||
      <span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-remove': settings.checkedStyle !== 'fontawesome','fa fa-times': settings.checkedStyle === 'fontawesome'}"></span></span>
 | 
			
		||||
      {{ texts.uncheckAll }}
 | 
			
		||||
    </a>
 | 
			
		||||
    <a *ngIf="settings.showCheckAll || settings.showUncheckAll" href="javascript:;" class="dropdown-divider divider"></a>
 | 
			
		||||
    <a *ngIf="!renderItems" href="javascript:;" class="dropdown-item empty">{{ texts.searchNoRenderText }}</a>
 | 
			
		||||
    <a *ngIf="renderItems && !renderFilteredOptions.length" href="javascript:;" class="dropdown-item empty">{{ texts.searchEmptyResult }}</a>
 | 
			
		||||
    <a class="dropdown-item" href="javascript:;" *ngFor="let option of renderFilteredOptions; trackBy: trackById" [class.active]="isSelected(option)"
 | 
			
		||||
      [ngStyle]="getItemStyle(option)" [ngClass]="option.classes" [class.dropdown-header]="option.isLabel" [ssAutofocus]="option !== focusedItem"
 | 
			
		||||
      tabindex="-1" (click)="setSelected($event, option)" (keydown.space)="setSelected($event, option)" (keydown.enter)="setSelected($event, option)">
 | 
			
		||||
      <span *ngIf="!option.isLabel; else label" role="menuitem" tabindex="-1" [style.padding-left]="this.parents.length>0&&this.parents.indexOf(option.id)<0&&'30px'"
 | 
			
		||||
        [ngStyle]="getItemStyleSelectionDisabled()">
 | 
			
		||||
        <ng-container [ngSwitch]="settings.checkedStyle">
 | 
			
		||||
          <input *ngSwitchCase="'checkboxes'" type="checkbox" [checked]="isSelected(option)" (click)="preventCheckboxCheck($event, option)"
 | 
			
		||||
            [disabled]="isCheckboxDisabled(option)" [ngStyle]="getItemStyleSelectionDisabled()" />
 | 
			
		||||
          <span *ngSwitchCase="'glyphicon'" style="width: 16px;" class="glyphicon" [class.glyphicon-ok]="isSelected(option)" [class.glyphicon-lock]="isCheckboxDisabled(option)"></span>
 | 
			
		||||
          <span *ngSwitchCase="'fontawesome'" style="width: 16px;display: inline-block;">
 | 
			
		||||
            <span *ngIf="isSelected(option)"><i class="fa fa-check" aria-hidden="true"></i></span>
 | 
			
		||||
            <span *ngIf="isCheckboxDisabled(option)"><i class="fa fa-lock" aria-hidden="true"></i></span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span *ngSwitchCase="'visual'" style="display:block;float:left; border-radius: 0.2em; border: 0.1em solid rgba(44, 44, 44, 0.63);background:rgba(0, 0, 0, 0.1);width: 5.5em;">
 | 
			
		||||
            <div class="slider" [ngClass]="{'slideron': isSelected(option)}">
 | 
			
		||||
              <img *ngIf="option.image != null" [src]="option.image" style="height: 100%; width: 100%; object-fit: contain" />
 | 
			
		||||
              <div *ngIf="option.image == null" style="height: 100%; width: 100%;text-align: center; display: table; background-color:rgba(0, 0, 0, 0.74)">
 | 
			
		||||
                <div class="content_wrapper">
 | 
			
		||||
                  <span style="font-size:3em;color:white" class="glyphicon glyphicon-eye-close"></span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </span>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
        <span [ngClass]="{'chunkyrow': settings.checkedStyle == 'visual' }" [class.disabled]="isCheckboxDisabled(option)" [ngClass]="settings.itemClasses"
 | 
			
		||||
          [style.font-weight]="this.parents.indexOf(option.id)>=0?'bold':'normal'">
 | 
			
		||||
          {{ option.name }}
 | 
			
		||||
        </span>
 | 
			
		||||
      </span>
 | 
			
		||||
      <ng-template #label>
 | 
			
		||||
        <span [class.disabled]="isCheckboxDisabled(option)">{{ option.name }}</span>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </a>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,710 @@
 | 
			
		||||
import {
 | 
			
		||||
  ChangeDetectionStrategy,
 | 
			
		||||
  ChangeDetectorRef,
 | 
			
		||||
  Component,
 | 
			
		||||
  DoCheck,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  forwardRef,
 | 
			
		||||
  Input,
 | 
			
		||||
  IterableDiffers,
 | 
			
		||||
  OnChanges,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  Output,
 | 
			
		||||
  SimpleChanges,
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  AbstractControl,
 | 
			
		||||
  ControlValueAccessor,
 | 
			
		||||
  FormBuilder,
 | 
			
		||||
  FormControl,
 | 
			
		||||
  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: FormControl = 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: FormBuilder,
 | 
			
		||||
    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');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,39 @@
 | 
			
		||||
import { Directive, HostListener } from '@angular/core';
 | 
			
		||||
import { EventEmitter } from '@angular/core';
 | 
			
		||||
import { Output } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Directive({
 | 
			
		||||
  // tslint:disable-next-line:directive-selector
 | 
			
		||||
  selector: '[offClick]',
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export class OffClickDirective {
 | 
			
		||||
  @Output('offClick') onOffClick = new EventEmitter<any>();
 | 
			
		||||
 | 
			
		||||
  private _clickEvent: MouseEvent;
 | 
			
		||||
  private _touchEvent: TouchEvent;
 | 
			
		||||
 | 
			
		||||
  @HostListener('click', ['$event']) 
 | 
			
		||||
  public onClick(event: MouseEvent): void {
 | 
			
		||||
    this._clickEvent = event;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('touchstart', ['$event'])
 | 
			
		||||
  public onTouch(event: TouchEvent): void {
 | 
			
		||||
    this._touchEvent = event;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('document:click', ['$event']) 
 | 
			
		||||
  public onDocumentClick(event: MouseEvent): void {
 | 
			
		||||
    if (event !== this._clickEvent) {
 | 
			
		||||
      this.onOffClick.emit(event);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('document:touchstart', ['$event'])
 | 
			
		||||
  public onDocumentTouch(event: TouchEvent): void {
 | 
			
		||||
    if (event !== this._touchEvent) {
 | 
			
		||||
      this.onOffClick.emit(event);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,130 @@
 | 
			
		||||
import { Pipe, PipeTransform } from '@angular/core';
 | 
			
		||||
import { IMultiSelectOption } from './types';
 | 
			
		||||
 | 
			
		||||
interface StringHashMap<T> {
 | 
			
		||||
  [k: string]: T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Pipe({
 | 
			
		||||
  name: 'searchFilter'
 | 
			
		||||
})
 | 
			
		||||
export class MultiSelectSearchFilter implements PipeTransform {
 | 
			
		||||
 | 
			
		||||
  private _lastOptions: IMultiSelectOption[];
 | 
			
		||||
  private _searchCache: StringHashMap<IMultiSelectOption[]> = {};
 | 
			
		||||
  private _searchCacheInclusive: StringHashMap<boolean | number> = {};
 | 
			
		||||
  private _prevSkippedItems: StringHashMap<number> = {};
 | 
			
		||||
 | 
			
		||||
  transform(
 | 
			
		||||
    options: IMultiSelectOption[],
 | 
			
		||||
    str = '',
 | 
			
		||||
    limit = 0,
 | 
			
		||||
    renderLimit = 0,
 | 
			
		||||
    searchFunction: (str: string) => RegExp,
 | 
			
		||||
  ): IMultiSelectOption[] {
 | 
			
		||||
    str = str.toLowerCase();
 | 
			
		||||
 | 
			
		||||
    // Drop cache because options were updated
 | 
			
		||||
    if (options !== this._lastOptions) {
 | 
			
		||||
      this._lastOptions = options;
 | 
			
		||||
      this._searchCache = {};
 | 
			
		||||
      this._searchCacheInclusive = {};
 | 
			
		||||
      this._prevSkippedItems = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const filteredOpts = this._searchCache.hasOwnProperty(str)
 | 
			
		||||
      ? this._searchCache[str]
 | 
			
		||||
      : this._doSearch(options, str, limit, searchFunction);
 | 
			
		||||
 | 
			
		||||
    const isUnderLimit = options.length <= limit;
 | 
			
		||||
 | 
			
		||||
    return isUnderLimit
 | 
			
		||||
      ? filteredOpts
 | 
			
		||||
      : this._limitRenderedItems(filteredOpts, renderLimit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _getSubsetOptions(
 | 
			
		||||
    options: IMultiSelectOption[],
 | 
			
		||||
    prevOptions: IMultiSelectOption[],
 | 
			
		||||
    prevSearchStr: string
 | 
			
		||||
  ) {
 | 
			
		||||
    const prevInclusiveOrIdx = this._searchCacheInclusive[prevSearchStr];
 | 
			
		||||
 | 
			
		||||
    if (prevInclusiveOrIdx === true) {
 | 
			
		||||
      // If have previous results and it was inclusive, do only subsearch
 | 
			
		||||
      return prevOptions;
 | 
			
		||||
    } else if (typeof prevInclusiveOrIdx === 'number') {
 | 
			
		||||
      // Or reuse prev results with unchecked ones
 | 
			
		||||
      return [...prevOptions, ...options.slice(prevInclusiveOrIdx)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _doSearch(options: IMultiSelectOption[], str: string, limit: number, searchFunction: (str: string) => RegExp) {
 | 
			
		||||
    const prevStr = str.slice(0, -1);
 | 
			
		||||
    const prevResults = this._searchCache[prevStr];
 | 
			
		||||
    const prevResultShift = this._prevSkippedItems[prevStr] || 0;
 | 
			
		||||
 | 
			
		||||
    if (prevResults) {
 | 
			
		||||
      options = this._getSubsetOptions(options, prevResults, prevStr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const optsLength = options.length;
 | 
			
		||||
    const maxFound = limit > 0 ? Math.min(limit, optsLength) : optsLength;
 | 
			
		||||
    const regexp = searchFunction(str);
 | 
			
		||||
    const filteredOpts: IMultiSelectOption[] = [];
 | 
			
		||||
 | 
			
		||||
    let i = 0, founded = 0, removedFromPrevResult = 0;
 | 
			
		||||
 | 
			
		||||
    const doesOptionMatch = (option: IMultiSelectOption) => regexp.test(option.name);
 | 
			
		||||
    const getChildren = (option: IMultiSelectOption) =>
 | 
			
		||||
      options.filter(child => child.parentId === option.id);
 | 
			
		||||
    const getParent = (option: IMultiSelectOption) =>
 | 
			
		||||
      options.find(parent => option.parentId === parent.id);
 | 
			
		||||
    const foundFn = (item: any) => { filteredOpts.push(item); founded++; };
 | 
			
		||||
    const notFoundFn = prevResults ? () => removedFromPrevResult++ : () => { };
 | 
			
		||||
 | 
			
		||||
    for (; i < optsLength && founded < maxFound; ++i) {
 | 
			
		||||
      const option = options[i];
 | 
			
		||||
      const directMatch = doesOptionMatch(option);
 | 
			
		||||
 | 
			
		||||
      if (directMatch) {
 | 
			
		||||
        foundFn(option);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (typeof option.parentId === 'undefined') {
 | 
			
		||||
        const childrenMatch = getChildren(option).some(doesOptionMatch);
 | 
			
		||||
 | 
			
		||||
        if (childrenMatch) {
 | 
			
		||||
          foundFn(option);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (typeof option.parentId !== 'undefined') {
 | 
			
		||||
        const parentMatch = doesOptionMatch(getParent(option));
 | 
			
		||||
 | 
			
		||||
        if (parentMatch) {
 | 
			
		||||
          foundFn(option);
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      notFoundFn();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const totalIterations = i + prevResultShift;
 | 
			
		||||
 | 
			
		||||
    this._searchCache[str] = filteredOpts;
 | 
			
		||||
    this._searchCacheInclusive[str] = i === optsLength || totalIterations;
 | 
			
		||||
    this._prevSkippedItems[str] = removedFromPrevResult + prevResultShift;
 | 
			
		||||
 | 
			
		||||
    return filteredOpts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _limitRenderedItems<T>(items: T[], limit: number): T[] {
 | 
			
		||||
    return items.length > limit && limit > 0 ? items.slice(0, limit) : items;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,82 @@
 | 
			
		||||
export interface IMultiSelectOption {
 | 
			
		||||
  id: any;
 | 
			
		||||
  name: string;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  isLabel?: boolean;
 | 
			
		||||
  parentId?: any;
 | 
			
		||||
  params?: any;
 | 
			
		||||
  classes?: string;
 | 
			
		||||
  image?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IMultiSelectSettings {
 | 
			
		||||
  pullRight?: boolean;
 | 
			
		||||
  enableSearch?: boolean;
 | 
			
		||||
  closeOnClickOutside?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * 0 - By default
 | 
			
		||||
   * If `enableSearch=true` and total amount of items more then `searchRenderLimit` (0 - No limit)
 | 
			
		||||
   * then render items only when user typed more then or equal `searchRenderAfter` charachters
 | 
			
		||||
   */
 | 
			
		||||
  searchRenderLimit?: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * 3 - By default
 | 
			
		||||
   */
 | 
			
		||||
  searchRenderAfter?: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * 0 - By default
 | 
			
		||||
   * If >0 will render only N first items
 | 
			
		||||
   */
 | 
			
		||||
  searchMaxLimit?: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * 0 - By default
 | 
			
		||||
   * Used with searchMaxLimit to further limit rendering for optimization
 | 
			
		||||
   * Should be less than searchMaxLimit to take effect
 | 
			
		||||
   */
 | 
			
		||||
  searchMaxRenderedItems?: number;
 | 
			
		||||
  checkedStyle?: 'checkboxes' | 'glyphicon' | 'fontawesome' | 'visual';
 | 
			
		||||
  buttonClasses?: string;
 | 
			
		||||
  itemClasses?: string;
 | 
			
		||||
  containerClasses?: string;
 | 
			
		||||
  selectionLimit?: number;
 | 
			
		||||
  minSelectionLimit?: number;
 | 
			
		||||
  closeOnSelect?: boolean;
 | 
			
		||||
  autoUnselect?: boolean;
 | 
			
		||||
  showCheckAll?: boolean;
 | 
			
		||||
  showUncheckAll?: boolean;
 | 
			
		||||
  fixedTitle?: boolean;
 | 
			
		||||
  dynamicTitleMaxItems?: number;
 | 
			
		||||
  maxHeight?: string;
 | 
			
		||||
  displayAllSelectedText?: boolean;
 | 
			
		||||
  isLazyLoad?: boolean;
 | 
			
		||||
  loadViewDistance?: number;
 | 
			
		||||
  stopScrollPropagation?: boolean;
 | 
			
		||||
  selectAddedValues?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * false - By default
 | 
			
		||||
   * If activated label IDs don't count and won't be written to the model.
 | 
			
		||||
   */
 | 
			
		||||
  ignoreLabels?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * false - By default
 | 
			
		||||
   * If activated, the title will show selections in the order they were selected.
 | 
			
		||||
   */
 | 
			
		||||
  maintainSelectionOrderInTitle?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * @default true
 | 
			
		||||
   * Set the focus back to the input control when the dropdown closed
 | 
			
		||||
   */
 | 
			
		||||
  focusBack?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IMultiSelectTexts {
 | 
			
		||||
  checkAll?: string;
 | 
			
		||||
  uncheckAll?: string;
 | 
			
		||||
  checked?: string;
 | 
			
		||||
  checkedPlural?: string;
 | 
			
		||||
  searchPlaceholder?: string;
 | 
			
		||||
  searchEmptyResult?: string;
 | 
			
		||||
  searchNoRenderText?: string;
 | 
			
		||||
  defaultTitle?: string;
 | 
			
		||||
  allSelected?: string;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user