UNPKG

novo-elements

Version:

943 lines (939 loc) 205 kB
import * as i0 from '@angular/core'; import { Input, Directive, Component, HostBinding, EventEmitter, Output, ViewEncapsulation, ViewChild, forwardRef, ViewContainerRef, NgModule } from '@angular/core'; import { from, fromEvent } from 'rxjs'; import { Helpers, notify } from 'novo-elements/utils'; import * as i1 from 'novo-elements/services'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i3 from 'novo-elements/elements/common'; import { NovoOverlayTemplateComponent, NovoCommonModule, NovoOverlayModule } from 'novo-elements/elements/common'; import * as i5 from 'novo-elements/elements/loading'; import { NovoLoadingModule } from 'novo-elements/elements/loading'; import * as i1$1 from '@angular/platform-browser'; import * as i6 from 'novo-elements/elements/list'; import { NovoListModule } from 'novo-elements/elements/list'; import * as i6$1 from 'novo-elements/pipes'; import { NovoPipesModule } from 'novo-elements/pipes'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import * as i3$1 from '@angular/forms'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import * as i7 from 'novo-elements/elements/switch'; import { NovoSwitchModule } from 'novo-elements/elements/switch'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; /** * @description This is the actual list of matches that gets injected into the DOM. It's also the piece that can be * overwritten if custom list options are needed. */ class BasePickerResults { set matches(m) { this._matches = m; } get matches() { return this.customTextValue ? [...this._matches, this.customTextValue] : this._matches; } constructor(element, ref) { this._term = ''; this.selected = []; this.hasError = false; this.isLoading = false; this.isStatic = true; this.page = 0; this.lastPage = false; this.autoSelectFirstOption = true; this.optionsFunctionHasChanged = false; this.selectingMatches = false; this._matches = []; this.customTextValue = null; this.element = element; this.ref = ref; this.scrollHandler = this.onScrollDown.bind(this); } cleanUp() { const element = this.getListElement(); if (element && element.hasAttribute('scrollListener')) { element.removeAttribute('scrollListener'); element.removeEventListener('scroll', this.scrollHandler); } } onScrollDown(event) { const element = event.target; if (element) { const offset = element.offsetHeight + element.scrollTop; const bottom = element.scrollHeight - 300; if (offset >= bottom) { event.stopPropagation(); if (!this.lastPage && !this.isLoading) { this.processSearch(); } } } } set term(value) { if (this.shouldSearch(value)) { this._term = value; this.page = 0; this.optionsFunctionHasChanged = false; this.matches = []; this.processSearch(true); } else { this.addScrollListener(); } } get term() { return this._term; } set config(value) { if (this.config && this.config.options !== value.options) { this.optionsFunctionHasChanged = true; // reset page so that new options call is used to search } this._config = value; } get config() { return this._config; } shouldSearch(value) { const termHasChanged = value !== this._term; const optionsNotYetCalled = this.page === 0; return termHasChanged || optionsNotYetCalled || this.optionsFunctionHasChanged; } addScrollListener() { if (this.config.enableInfiniteScroll) { const element = this.getListElement(); if (element && !element.hasAttribute('scrollListener')) { element.setAttribute('scrollListener', 'true'); element.addEventListener('scroll', this.scrollHandler); } } } processSearch(shouldReset) { this.hasError = false; this.isLoading = true; this.ref.markForCheck(); this.search(this.term).subscribe((results) => { if (shouldReset) { this.matches = []; } if (this.isStatic) { this.matches = this.filterData(results); } else { this.matches = this.matches.concat(results); this.lastPage = results && !results.length; } if (this.matches.length > 0 && this.autoSelectFirstOption && !this.selectingMatches) { this.nextActiveMatch(); } this.isLoading = false; this.ref.markForCheck(); setTimeout(() => { this.overlay.updatePosition(); this.addScrollListener(); }); }, (err) => { this.hasError = this.term && this.term.length !== 0; this.isLoading = false; this.lastPage = true; if (this.term && this.term.length !== 0) { console.error(err); } this.ref.markForCheck(); }); } search(term, mode) { const options = this.config.options; return from(new Promise((resolve, reject) => { // Check if there is match data if (options) { // Resolve the data if (Array.isArray(options)) { this.isStatic = true; // Arrays are returned immediately resolve(this.structureArray(options)); } else if (this.shouldCallOptionsFunction(term)) { if ((options.hasOwnProperty('reject') && options.hasOwnProperty('resolve')) || Object.getPrototypeOf(options).hasOwnProperty('then')) { this.isStatic = false; // Promises (ES6 or Deferred) are resolved whenever they resolve options.then(this.structureArray.bind(this)).then(resolve, reject); } else if (typeof options === 'function') { this.isStatic = false; // Promises (ES6 or Deferred) are resolved whenever they resolve options(term, ++this.page) .then(this.structureArray.bind(this)) .then(resolve, reject); } else { // All other kinds of data are rejected reject('The data provided is not an array or a promise'); throw new Error('The data provided is not an array or a promise'); } } else { if (this.config.defaultOptions) { this.isStatic = false; if (typeof this.config.defaultOptions === 'function') { const defaultOptions = this.config.defaultOptions(term, ++this.page); if (Object.getPrototypeOf(defaultOptions).hasOwnProperty('then')) { defaultOptions.then(this.structureArray.bind(this)).then(resolve, reject); } else { resolve(this.structureArray(defaultOptions)); } } else { resolve(this.structureArray(this.config.defaultOptions)); } } else { // No search term gets rejected reject('No search term'); } } } else { // No data gets rejected reject('error'); } })); } shouldCallOptionsFunction(term) { if (this.config && 'minSearchLength' in this.config && Number.isInteger(this.config.minSearchLength)) { return typeof term === 'string' && term.length >= this.config.minSearchLength; } else { return !!(term && term.length); } } /** * @param collection - the data once getData resolves it * * @description This function structures an array of nodes into an array of objects with a * 'name' field by default. */ structureArray(collection) { const dataArray = collection.data ? collection.data : collection; if (dataArray && (typeof dataArray[0] === 'string' || typeof dataArray[0] === 'number')) { return collection.map((item) => { return { value: item, label: item, }; }); } return dataArray.map((data) => { let value = this.config.field ? data[this.config.field] : data.value || data; if (this.config.valueFormat) { value = Helpers.interpolate(this.config.valueFormat, data); } const label = this.config.format ? Helpers.interpolate(this.config.format, data) : data.label || String(value); return { value, label, data }; }); } /** * @param matches - Collection of objects= * * @description This function loops through the picker options and creates a filtered list of objects that contain * the newSearch. */ filterData(matches) { if (this.term && matches) { return matches.filter((match) => { return ~String(match.label).toLowerCase().indexOf(this.term.toLowerCase()); }); } // Show no recent results template return matches; } /** * @description This function is called when the user presses the enter key to call the selectMatch method. */ selectActiveMatch() { this.selectMatch(); } /** * @description This function sets activeMatch to the match before the current node. */ prevActiveMatch() { const index = this.matches.indexOf(this.activeMatch); this.activeMatch = this.matches[index - 1 < 0 ? this.matches.length - 1 : index - 1]; this.scrollToActive(); this.ref.markForCheck(); } /** * @description This function sets activeMatch to the match after the current node. */ nextActiveMatch() { const index = this.matches.indexOf(this.activeMatch); this.activeMatch = this.matches[index + 1 > this.matches.length - 1 ? 0 : index + 1]; this.scrollToActive(); this.ref.markForCheck(); } getListElement() { return this.element.nativeElement; } getChildrenOfListElement() { let children = []; if (this.getListElement()) { children = this.getListElement().children; } return children; } scrollToActive() { const list = this.getListElement(); const items = this.getChildrenOfListElement(); const index = this.matches.indexOf(this.activeMatch); const item = items[index]; if (item) { list.scrollTop = item.offsetTop; } } /** * @description */ selectActive(match) { this.activeMatch = match; } /** * @description */ isActive(match) { return this.activeMatch === match; } /** * @description */ selectMatch(event, item) { if (event) { event.stopPropagation(); event.preventDefault(); } const selected = this.activeMatch; if (selected && this.parent) { this.parent.value = selected; this.selectingMatches = true; if (this.parent.closeOnSelect) { this.parent.hideResults(); } this.selectingMatches = false; } this.ref.markForCheck(); return false; } /** * @description This function captures the whole query string and replace it with the string that will be used to * match. */ escapeRegexp(queryToEscape) { // Ex: if the capture is "a" the result will be \a return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } /** * @deprecated use highlight pipe */ highlight(match, query) { // Replaces the capture string with a the same string inside of a "strong" tag return query ? match.replace(new RegExp(this.escapeRegexp(query.trim()), 'gi'), '<strong>$&</strong>') : match; } preselected(match) { let selected = this.selected; if (this.config && this.config.selected) { selected = [...this.selected, ...this.config.selected]; } if (this.config && this.config.preselected) { const preselectedFunc = this.config.preselected; return (selected.findIndex((item) => { return preselectedFunc(match, item); }) !== -1); } return (selected.findIndex((item) => { let isPreselected = false; if (item && item.value && match && match.value) { if (item.value.id && match.value.id) { isPreselected = item.value.id === match.value.id; } else if (item.value instanceof Object && item.value.hasOwnProperty('value')) { isPreselected = item.value.value === match.value; } else { isPreselected = item.value === match.value; } } return isPreselected; }) !== -1); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: BasePickerResults, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: BasePickerResults, isStandalone: true, inputs: { matches: "matches", term: "term" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: BasePickerResults, decorators: [{ type: Directive }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { matches: [{ type: Input }], term: [{ type: Input }] } }); // NG2 /** * @description This is the actual list of matches that gets injected into the DOM. */ class ChecklistPickerResults extends BasePickerResults { constructor(element, labels, ref) { super(element, ref); this.labels = labels; } search() { const options = this.config.options; // only set this the first time return from(new Promise((resolve, reject) => { // Check if there is match data if (options) { // Resolve the data if (Array.isArray(options)) { this.isStatic = true; // Arrays are returned immediately resolve(options); } else { // All other kinds of data are rejected reject('The data provided is not an array or a promise'); throw new Error('The data provided is not an array or a promise'); } } else { // No data gets rejected reject('error'); } })); } /** * @param matches - Collection of objects= * * @description This function loops through the picker options and creates a filtered list of objects that contain * the newSearch. */ filterData(matches) { if (this.term && matches) { this.filteredMatches = matches.map((section) => { const items = section.originalData.filter((match) => { return ~String(match.label).toLowerCase().indexOf(this.term.toLowerCase()); }); section.data = items; return section; }, this); return this.filteredMatches; } else if (this.term === '') { matches.forEach((section) => { section.data = section.originalData; }); return matches; } // Show no recent results template return matches; } selectMatch(event, item) { Helpers.swallowEvent(event); if (item.indeterminate) { item.indeterminate = false; item.checked = true; } else { item.checked = !item.checked; } const selected = this.activeMatch; if (selected) { this.parent.value = selected; } this.ref.markForCheck(); return false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ChecklistPickerResults, deps: [{ token: i0.ElementRef }, { token: i1.NovoLabelService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: ChecklistPickerResults, isStandalone: false, selector: "checklist-picker-results", host: { classAttribute: "active picker-results" }, usesInheritance: true, ngImport: i0, template: ` <novo-loading theme="line" *ngIf="isLoading && !matches.length"></novo-loading> <ul *ngIf="matches.length > 0"> <span *ngFor="let section of matches; let i = index"> <li class="header caption" *ngIf="section.data.length > 0">{{ section.label || section.type }}</li> <li *ngFor="let match of section.data; let i = index" [ngClass]="{ checked: match.checked }" (click)="selectMatch($event, match)" [class.active]="match === activeMatch" (mouseenter)="selectActive(match)" > <label> <i [ngClass]="{ 'bhi-checkbox-empty': !match.checked, 'bhi-checkbox-filled': match.checked, 'bhi-checkbox-indeterminate': match.indeterminate }" ></i> {{ match.label }} </label> </li> </span> </ul> <p class="picker-error" *ngIf="hasError">{{ labels.pickerError }}</p> <p class="picker-null-results" *ngIf="!isLoading && !matches.length && !hasError && term !== ''">{{ labels.pickerEmpty }}</p> `, isInline: true, dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.ThemeColorDirective, selector: "[theme]", inputs: ["theme"] }, { kind: "component", type: i5.NovoLoadingElement, selector: "novo-loading", inputs: ["theme", "color", "size"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ChecklistPickerResults, decorators: [{ type: Component, args: [{ selector: 'checklist-picker-results', host: { class: 'active picker-results', }, template: ` <novo-loading theme="line" *ngIf="isLoading && !matches.length"></novo-loading> <ul *ngIf="matches.length > 0"> <span *ngFor="let section of matches; let i = index"> <li class="header caption" *ngIf="section.data.length > 0">{{ section.label || section.type }}</li> <li *ngFor="let match of section.data; let i = index" [ngClass]="{ checked: match.checked }" (click)="selectMatch($event, match)" [class.active]="match === activeMatch" (mouseenter)="selectActive(match)" > <label> <i [ngClass]="{ 'bhi-checkbox-empty': !match.checked, 'bhi-checkbox-filled': match.checked, 'bhi-checkbox-indeterminate': match.indeterminate }" ></i> {{ match.label }} </label> </li> </span> </ul> <p class="picker-error" *ngIf="hasError">{{ labels.pickerError }}</p> <p class="picker-null-results" *ngIf="!isLoading && !matches.length && !hasError && term !== ''">{{ labels.pickerEmpty }}</p> `, standalone: false, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.NovoLabelService }, { type: i0.ChangeDetectorRef }] }); // NG2 class DistributionListPickerResults extends BasePickerResults { get isHidden() { return this.matches.length === 0; } constructor(element, sanitizer, labels, ref) { super(element, ref); this.sanitizer = sanitizer; this.labels = labels; this.active = true; this.sanitizer = sanitizer; } getListElement() { return this.element.nativeElement.querySelector('novo-list'); } sanitizeHTML(html) { return this.sanitizer.bypassSecurityTrustHtml(html); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DistributionListPickerResults, deps: [{ token: i0.ElementRef }, { token: i1$1.DomSanitizer }, { token: i1.NovoLabelService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DistributionListPickerResults, isStandalone: false, selector: "distribution-list-picker-results", host: { properties: { "class.active": "this.active", "hidden": "this.isHidden" } }, usesInheritance: true, ngImport: i0, template: ` <section class="picker-loading" *ngIf="isLoading && !matches?.length"> <novo-loading theme="line"></novo-loading> </section> <novo-list direction="vertical" *ngIf="matches?.length > 0 && !hasError"> <novo-list-item *ngFor="let match of matches" (click)="selectMatch($event)" [class.active]="match === activeMatch" (mouseenter)="selectActive(match)" [class.disabled]="preselected(match)" > <item-header> <item-title> <span [innerHtml]="sanitizeHTML(match.label)"></span> </item-title> </item-header> <item-content direction="horizontal"> <p> <span class="label">{{ labels.distributionListOwner }}: </span><span>{{ match?.data?.owner?.name }}</span> </p> <p> <span class="label">{{ labels.dateAdded }}: </span ><span>{{ labels.formatDateWithFormat(match?.data?.dateAdded, { year: 'numeric', month: 'numeric', day: 'numeric' }) }}</span> </p> </item-content> </novo-list-item> <novo-loading theme="line" *ngIf="isLoading && matches?.length > 0"></novo-loading> </novo-list> `, isInline: true, dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.ThemeColorDirective, selector: "[theme]", inputs: ["theme"] }, { kind: "component", type: i5.NovoLoadingElement, selector: "novo-loading", inputs: ["theme", "color", "size"] }, { kind: "component", type: i6.NovoListElement, selector: "novo-list", inputs: ["theme", "direction"] }, { kind: "component", type: i6.NovoListItemElement, selector: "novo-list-item, a[list-item], button[list-item]" }, { kind: "component", type: i6.NovoItemTitleElement, selector: "item-title, novo-item-title" }, { kind: "component", type: i6.NovoItemHeaderElement, selector: "item-header, novo-item-header" }, { kind: "component", type: i6.NovoItemContentElement, selector: "item-content, novo-item-content", inputs: ["direction"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DistributionListPickerResults, decorators: [{ type: Component, args: [{ selector: 'distribution-list-picker-results', template: ` <section class="picker-loading" *ngIf="isLoading && !matches?.length"> <novo-loading theme="line"></novo-loading> </section> <novo-list direction="vertical" *ngIf="matches?.length > 0 && !hasError"> <novo-list-item *ngFor="let match of matches" (click)="selectMatch($event)" [class.active]="match === activeMatch" (mouseenter)="selectActive(match)" [class.disabled]="preselected(match)" > <item-header> <item-title> <span [innerHtml]="sanitizeHTML(match.label)"></span> </item-title> </item-header> <item-content direction="horizontal"> <p> <span class="label">{{ labels.distributionListOwner }}: </span><span>{{ match?.data?.owner?.name }}</span> </p> <p> <span class="label">{{ labels.dateAdded }}: </span ><span>{{ labels.formatDateWithFormat(match?.data?.dateAdded, { year: 'numeric', month: 'numeric', day: 'numeric' }) }}</span> </p> </item-content> </novo-list-item> <novo-loading theme="line" *ngIf="isLoading && matches?.length > 0"></novo-loading> </novo-list> `, standalone: false, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.DomSanitizer }, { type: i1.NovoLabelService }, { type: i0.ChangeDetectorRef }], propDecorators: { active: [{ type: HostBinding, args: ['class.active'] }], isHidden: [{ type: HostBinding, args: ['hidden'] }] } }); class EntityPickerResult { constructor(labels) { this.labels = labels; this.select = new EventEmitter(); } /** * @description This function captures the whole query string and replace it with the string that will be used to * match. */ escapeRegexp(queryToEscape) { // Ex: if the capture is "a" the result will be \a return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } /** * @deprecated use highlight pipe */ highlight(match, query) { // Replaces the capture string with a the same string inside of a "strong" tag return query && match ? match.replace(new RegExp(this.escapeRegexp(query.trim()), 'gi'), '<strong>$&</strong>') : match; } getIconForResult(result) { if (result) { switch (result.searchEntity) { case 'ClientContact': return 'person contact'; case 'ClientCorporation': return 'company'; case 'Opportunity': return 'opportunity'; case 'Candidate': return 'candidate'; case 'Lead': return 'lead'; case 'JobOrder': return 'job'; case 'Placement': return 'star placement'; case 'CorporateUser': return 'user'; case 'CorporationDepartment': return 'department'; case 'JobShift': return 'timetable contract'; default: return ''; } } return ''; } renderTimestamp(date) { let timestamp = ''; if (date) { timestamp = this.labels.formatDateWithFormat(date, { year: 'numeric', month: 'numeric', day: 'numeric' }); } return timestamp; } renderTime(dateStr) { let timestamp = ''; if (dateStr) { timestamp = this.labels.formatTime(new Date(dateStr)); } return timestamp; } renderTimeNoOffset(dateStr) { let timestamp = ''; if (dateStr) { dateStr = dateStr.slice(0, 19); timestamp = this.labels.formatTime(dateStr); } return timestamp; } getNameForResult(result) { if (result) { switch (result.searchEntity) { case 'Lead': case 'CorporateUser': case 'ClientContact': case 'Candidate': case 'Person': if ('firstName' in result) { return `${result.firstName} ${result.lastName}`.trim(); } return `${result.name || ''}`.trim(); case 'ClientCorporation': return `${result.name || ''}`.trim(); case 'Opportunity': case 'JobOrder': case 'BillingProfile': case 'InvoiceTerm': return `${result.id} | ${result.title || ''}`.trim(); case 'Placement': let label = `${result.id}`; if (result.candidate || result.jobOrder) { if (result.candidate && result.jobOrder) { label = `${label} | ${result.candidate.firstName} ${result.candidate.lastName} - ${result.jobOrder.title}`.trim(); } else if (result.jobOrder) { label = `${label} | ${result.jobOrder.title}`.trim(); } else { label = `${label} | ${result.candidate.firstName} ${result.candidate.lastName}`.trim(); } } return label; case 'JobShift': return `${result.jobOrder?.title} @ ${result.jobOrder?.clientCorporation?.name || ''}`.trim(); default: return `${result.name || result.label || ''}`.trim(); } } return ''; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: EntityPickerResult, deps: [{ token: i1.NovoLabelService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: EntityPickerResult, isStandalone: false, selector: "entity-picker-result", inputs: { match: "match", term: "term" }, outputs: { select: "select" }, ngImport: i0, template: ` <novo-list-item *ngIf="match.data" (click)="select.next(match.data)"> <novo-item-header> <novo-item-avatar [icon]="getIconForResult(match.data)"></novo-item-avatar> <novo-item-title> <span [innerHtml]="getNameForResult(match.data) | highlight:term"></span> </novo-item-title> </novo-item-header> <novo-item-content direction="horizontal"> <!-- COMPANY 1 --> <novo-text smaller class="company" *ngIf="match.data.companyName || match.data?.clientCorporation?.name"> <i class="bhi-company company"></i> <span [innerHtml]="match.data.companyName || match.data?.clientCorporation?.name | highlight:term"></span> </novo-text> <!-- CLIENT CONTACT --> <novo-text smaller class="contact" *ngIf="match.data?.clientContact?.firstName"> <i class="bhi-person contact person"></i> <span [innerHtml]="match.data.clientContact.firstName + ' ' + match.data.clientContact.lastName | highlight:term"></span> </novo-text> <!-- CANDIDATE --> <novo-text smaller class="candidate" *ngIf="match.data.candidate && match.data.searchEntity === 'Placement'"> <i class="bhi-candidate candidate"></i> <span [innerHtml]="match.data.candidate.firstName + ' ' + match.data.candidate.lastName | highlight:term"></span> </novo-text> <!-- START & END DATE --> <novo-text smaller class="start-date" *ngIf="match.data.dateBegin && match.data.searchEntity === 'Placement'"> <i class="bhi-calendar"></i> <span [innerHtml]="renderTimestamp(match.data.dateBegin) + ' - ' + renderTimestamp(match.data.dateEnd)"></span> </novo-text> <!-- START Date --> <novo-text smaller class="start-date" *ngIf="match.data.startTime && match.data.searchEntity === 'JobShift'"> <i class="bhi-calendar"></i> <span [innerHtml]="renderTimestamp(match.data.startTime)"></span> </novo-text> <!-- START & END TIME --> <novo-text smaller class="start-time" *ngIf="match.data.startTime && match.data.searchEntity === 'JobShift'"> <i class="bhi-clock"></i> <span [innerHtml]="renderTimeNoOffset(match.data.startTime) + ' - ' + renderTimeNoOffset(match.data.endTime)"></span> </novo-text> <!-- JOBORDER --> <novo-text smaller class="job" *ngIf="match.data.jobOrder && match.data.searchEntity === 'JobShift'"> <i class="bhi-job job"></i> <span [innerHtml]="match.data.jobOrder.title | highlight:term"></span> </novo-text> <!-- OPENINGS --> <novo-text smaller class="openings" *ngIf="match.data.openings && match.data.searchEntity === 'JobShift'"> <i class="bhi-candidate"></i> <span>{{ match.data.numAssigned }} / {{ match.data.openings }}</span> </novo-text> <!-- EMAIL --> <novo-text smaller class="email" *ngIf="match.data.email"> <i class="bhi-email"></i> <span [innerHtml]="match.data.email | highlight:term"></span> </novo-text> <!-- PHONE --> <novo-text smaller class="phone" *ngIf="match.data.phone"> <i class="bhi-phone"></i> <span [innerHtml]="match.data.phone | highlight:term"></span> </novo-text> <!-- ADDRESS --> <novo-text smaller class="location" *ngIf="match.data.address && (match.data.address.city || match.data.address.state)"> <i class="bhi-location"></i> <span *ngIf="match.data.address.city" [innerHtml]="highlight(match.data.address.city, term)"></span> <span *ngIf="match.data.address.city && match.data.address.state">, </span> <span *ngIf="match.data.address.state" [innerHtml]="match.data.address.state | highlight:term"></span> </novo-text> <!-- STATUS --> <novo-text smaller class="status" *ngIf="match.data.status"> <i class="bhi-info"></i> <span [innerHtml]="match.data.status | highlight:term"></span> </novo-text> <!-- OWNER --> <novo-text smaller class="owner" *ngIf="match.data.owner && match.data.owner.name && match.data.searchEntity === 'Candidate'"> <i class="bhi-person"></i> <span [innerHtml]="match.data.owner.name | highlight:term"></span> </novo-text> <!-- PRIMARY DEPARTMENT --> <novo-text smaller class="primary-department" *ngIf="match.data.primaryDepartment && match.data.primaryDepartment.name && match.data.searchEntity === 'CorporateUser'" > <i class="bhi-department"></i> <span [innerHtml]="match.data.primaryDepartment.name | highlight:term"></span> </novo-text> <!-- OCCUPATION --> <novo-text smaller class="occupation" *ngIf="match.data.occupation && match.data.searchEntity === 'CorporateUser'"> <i class="bhi-occupation"></i> <span [innerHtml]="match.data.occupation | highlight:term"></span> </novo-text> </novo-item-content> </novo-list-item> `, isInline: true, styles: [":host(.disabled){opacity:.5;pointer-events:none}:host(.active)>novo-list-item{background-color:#e0ebf9}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.NovoText, selector: "novo-text,[novo-text]", inputs: ["block"] }, { kind: "component", type: i6.NovoListItemElement, selector: "novo-list-item, a[list-item], button[list-item]" }, { kind: "component", type: i6.NovoItemAvatarElement, selector: "item-avatar, novo-item-avatar", inputs: ["icon", "color"] }, { kind: "component", type: i6.NovoItemTitleElement, selector: "item-title, novo-item-title" }, { kind: "component", type: i6.NovoItemHeaderElement, selector: "item-header, novo-item-header" }, { kind: "component", type: i6.NovoItemContentElement, selector: "item-content, novo-item-content", inputs: ["direction"] }, { kind: "pipe", type: i6$1.HighlightPipe, name: "highlight" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: EntityPickerResult, decorators: [{ type: Component, args: [{ selector: 'entity-picker-result', template: ` <novo-list-item *ngIf="match.data" (click)="select.next(match.data)"> <novo-item-header> <novo-item-avatar [icon]="getIconForResult(match.data)"></novo-item-avatar> <novo-item-title> <span [innerHtml]="getNameForResult(match.data) | highlight:term"></span> </novo-item-title> </novo-item-header> <novo-item-content direction="horizontal"> <!-- COMPANY 1 --> <novo-text smaller class="company" *ngIf="match.data.companyName || match.data?.clientCorporation?.name"> <i class="bhi-company company"></i> <span [innerHtml]="match.data.companyName || match.data?.clientCorporation?.name | highlight:term"></span> </novo-text> <!-- CLIENT CONTACT --> <novo-text smaller class="contact" *ngIf="match.data?.clientContact?.firstName"> <i class="bhi-person contact person"></i> <span [innerHtml]="match.data.clientContact.firstName + ' ' + match.data.clientContact.lastName | highlight:term"></span> </novo-text> <!-- CANDIDATE --> <novo-text smaller class="candidate" *ngIf="match.data.candidate && match.data.searchEntity === 'Placement'"> <i class="bhi-candidate candidate"></i> <span [innerHtml]="match.data.candidate.firstName + ' ' + match.data.candidate.lastName | highlight:term"></span> </novo-text> <!-- START & END DATE --> <novo-text smaller class="start-date" *ngIf="match.data.dateBegin && match.data.searchEntity === 'Placement'"> <i class="bhi-calendar"></i> <span [innerHtml]="renderTimestamp(match.data.dateBegin) + ' - ' + renderTimestamp(match.data.dateEnd)"></span> </novo-text> <!-- START Date --> <novo-text smaller class="start-date" *ngIf="match.data.startTime && match.data.searchEntity === 'JobShift'"> <i class="bhi-calendar"></i> <span [innerHtml]="renderTimestamp(match.data.startTime)"></span> </novo-text> <!-- START & END TIME --> <novo-text smaller class="start-time" *ngIf="match.data.startTime && match.data.searchEntity === 'JobShift'"> <i class="bhi-clock"></i> <span [innerHtml]="renderTimeNoOffset(match.data.startTime) + ' - ' + renderTimeNoOffset(match.data.endTime)"></span> </novo-text> <!-- JOBORDER --> <novo-text smaller class="job" *ngIf="match.data.jobOrder && match.data.searchEntity === 'JobShift'"> <i class="bhi-job job"></i> <span [innerHtml]="match.data.jobOrder.title | highlight:term"></span> </novo-text> <!-- OPENINGS --> <novo-text smaller class="openings" *ngIf="match.data.openings && match.data.searchEntity === 'JobShift'"> <i class="bhi-candidate"></i> <span>{{ match.data.numAssigned }} / {{ match.data.openings }}</span> </novo-text> <!-- EMAIL --> <novo-text smaller class="email" *ngIf="match.data.email"> <i class="bhi-email"></i> <span [innerHtml]="match.data.email | highlight:term"></span> </novo-text> <!-- PHONE --> <novo-text smaller class="phone" *ngIf="match.data.phone"> <i class="bhi-phone"></i> <span [innerHtml]="match.data.phone | highlight:term"></span> </novo-text> <!-- ADDRESS --> <novo-text smaller class="location" *ngIf="match.data.address && (match.data.address.city || match.data.address.state)"> <i class="bhi-location"></i> <span *ngIf="match.data.address.city" [innerHtml]="highlight(match.data.address.city, term)"></span> <span *ngIf="match.data.address.city && match.data.address.state">, </span> <span *ngIf="match.data.address.state" [innerHtml]="match.data.address.state | highlight:term"></span> </novo-text> <!-- STATUS --> <novo-text smaller class="status" *ngIf="match.data.status"> <i class="bhi-info"></i> <span [innerHtml]="match.data.status | highlight:term"></span> </novo-text> <!-- OWNER --> <novo-text smaller class="owner" *ngIf="match.data.owner && match.data.owner.name && match.data.searchEntity === 'Candidate'"> <i class="bhi-person"></i> <span [innerHtml]="match.data.owner.name | highlight:term"></span> </novo-text> <!-- PRIMARY DEPARTMENT --> <novo-text smaller class="primary-department" *ngIf="match.data.primaryDepartment && match.data.primaryDepartment.name && match.data.searchEntity === 'CorporateUser'" > <i class="bhi-department"></i> <span [innerHtml]="match.data.primaryDepartment.name | highlight:term"></span> </novo-text> <!-- OCCUPATION --> <novo-text smaller class="occupation" *ngIf="match.data.occupation && match.data.searchEntity === 'CorporateUser'"> <i class="bhi-occupation"></i> <span [innerHtml]="match.data.occupation | highlight:term"></span> </novo-text> </novo-item-content> </novo-list-item> `, standalone: false, styles: [":host(.disabled){opacity:.5;pointer-events:none}:host(.active)>novo-list-item{background-color:#e0ebf9}\n"] }] }], ctorParameters: () => [{ type: i1.NovoLabelService }], propDecorators: { match: [{ type: Input }], term: [{ type: Input }], select: [{ type: Output }] } }); class EntityPickerResults extends BasePickerResults { constructor(element, labels, ref) { super(element, ref); this.labels = labels; this.select = new EventEmitter(); } get hasNonErrorMessage() { return !this.isLoading && !this.matches.length && !this.hasError; } getListElement() { return this.element.nativeElement.querySelector('novo-list'); } selectMatch(event, item) { this.select.next(item); return super.selectMatch(event, item); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: EntityPickerResults, deps: [{ token: i0.ElementRef }, { token: i1.NovoLabelService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: EntityPickerResults, isStandalone: false, selector: "entity-picker-results", outputs: { select: "select" }, host: { classAttribute: "novo-entity-picker-results" }, usesInheritance: true, ngImport: i0, template: ` <novo-list *ngIf="matches.length > 0" direction="vertical"> <entity-picker-result *ngFor="let match of matches" [match]="match" [term]="term" [ngClass]="{ active: match === activeMatch }" (click)="selectMatch($event, match)" (mouseenter)="selectActive(match)" [class.disabled]="preselected(match)" > </entity-picker-result> <novo-loading theme="line" *ngIf="isLoading && matches.length > 0"></novo-loading> </novo-list> <div class="picker-error" *ngIf="hasError">{{ labels.pickerError }}</div> <div class="picker-null-results" *ngIf="hasNonErrorMessage && term !== ''">{{ labels.pickerEmpty }}</div> <div class="picker-null-results" *ngIf="hasNonErrorMessage && term === ''">{{ labels.pickerTextFieldEmpty }}</div> `, isInline: true, styles: ["picker-results,entity-picker-results{background:#fff;color:#000;min-width:100%;max-width:100%;z-index:10;top:100%}picker-results .novo-list,entity-picker-results .novo-list{border:1px solid #4a89dc}picker-results .novo-list .novo-list-item,entity-picker-results .novo-list .novo-list-item{cursor:pointer;flex:0 0;transition:background-color .25s}picker-results .novo-list .novo-list-item>div,entity-picker-results .novo-list .novo-list-item>div{width:100%}picker-results .novo-list .novo-list-item.active,entity-picker-results .novo-list .novo-list-item.active{background-color:#e0ebf9}picker-results .novo-list .novo-list-item:hover,entity-picker-results .novo-list .novo-list-item:hover{background-color:#f1f6fc}picker-results .novo-list .novo-list-item .novo-item-content,entity-picker-results .novo-list .novo-list-item .novo-item-content{flex-flow:row wrap}picker-results .novo-list .novo-list-item .novo-item-content>*,entity-picker-results .novo-list .novo-list-item .novo-item-content>*{flex:0 0 33%;white-space:nowrap}picker-results picker-error,picker-results picker-loader,picker-results picker-null-recent-results,picker-results picker-null-results,picker-results .picker-error,picker-results .picker-loader,picker-results .picker-null-recent-results,picker-results .picker-null-results,entity-picker-results picker-error,entity-picker-results picker-loader,entity-picker-results picker-null-recent-results,entity-picker-results picker-null-results,entity-picker-results .picker-error,entity-picker-results .picker-loader,entity-picker-results .picker-null-recent-results,entity-picker-results .picker-null-results{background-color:#fff;text-align:center;color:#b5b5b5;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;border:1px solid #4a89dc;transform:translateY(0);transition:all .15s cubic-bezier(.35,0,.25,1);padding:.5rem}picker-results p.picker-error,picker-results p.picker-loader,picker-results p.picker-null-recent-results,picker-results p.picker-null-results,entity-picker-results p.picker-error,entity-picker-results p.picker-loader,entity-picker-results p.picker-null-recent-results,entity-picker-results p.picker-null-results{max-width:inherit;padding:5px}picker-results picker-loader,picker-results .picker-loader,entity-picker-results picker-loader,entity-picker-results .picker-loader{background-color:#fff;display:flex;align-items:center;flex-direction:column;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;border:1px solid #4a89dc;transform:translateY(0);transition:all .15s cubic-bezier(.35,0,.25,1)}picker-results section,entity-picker-results section{box-shadow:.1em .1em 1em #00000040;z-index:9;position:absolute;width:100%;background-color:#fff;color:#000}picker-results,.picker-results,quick-note-results,.quick-note-results{background-color:#fff;cursor:default;line-height:26px;width:100%;display:block}picker-results novo-list,picker-results ul,.picker-results novo-list,.picker-results ul,quick-note-results novo-list,quick-note-results ul,.quick-note-results novo-list,.quick-note-results ul{background-color:#fff;max-height:200px;overflow:auto;list-style:none;padding:0;margin:0;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;border:1px solid #4a89dc;transform:translateY(0);transition:all .15s cubic-bezier(.35,0,.25,1);display:block}picker-results novo-list novo-list-item,picker-results novo-list li,picker-results ul novo-list-item,picker-results ul li,.picker-results novo-list novo-list-item,.picker-results novo-list li,.picker-results ul novo-list-item,.picker-results ul li,quick-note-results novo-list novo-list-item,quick-note-results novo-list li,quick-note-results ul novo-list-item,quick-note-results ul li,.quick-note-results novo-list novo-list-item,.quick-note-results novo-list li,.quick-note-results ul novo-list-item,.quick-note-results ul li{font-size:.9em;padding:5px 16px}picker-results novo-list novo-list-item span,picker-results novo-list li span,picker-results ul novo-list-item span,picker-results ul li span,.picker-results novo-list novo-list-item span,.picker-results novo-list li span,.picker-results ul novo-list-item span,.picker-results ul li span,quick-note-results novo-list novo-list-item span,quick-note-results novo-list li span,quick-note-results ul novo-list-item span,quick-note-results ul li span,.quick-note-results novo-list novo-list-item span,.quick-note-results novo-list li span,.quick-note-results ul novo-list-item span,.quick-note-results ul li span{display:inline-block;min-width:100px;margin:2px 0}picker-results novo-list novo-list-item h6,picker-results novo-list li h6,picker-results ul novo-list-item h6,picker-results ul li h6,.picker-results novo-list novo-list-item h6,.picker-results novo-list li h6,.picker-results ul novo-list-item h6,.picker-results ul li h6,quick-note-results novo-list novo-list-item h6,quick-note-results novo-list li h6,quick-note-results ul novo-list-it