novo-elements
Version:
943 lines (939 loc) • 205 kB
JavaScript
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