UNPKG

novo-elements

Version:

1,035 lines (1,024 loc) 235 kB
import * as i1 from '@angular/cdk/table'; import { CdkHeaderCell, CdkCell, DataSource, CdkHeaderRow, CDK_ROW_TEMPLATE, CdkRow, CdkTableModule } from '@angular/cdk/table'; import * as i0 from '@angular/core'; import { InjectionToken, Input, HostBinding, Inject, ChangeDetectionStrategy, Component, EventEmitter, Injectable, Output, Directive, HostListener, ViewChild, Optional, Pipe, ViewChildren, ContentChildren, ViewEncapsulation, NgModule } from '@angular/core'; import * as i2 from 'novo-elements/elements/toast'; import { Subject, fromEvent, merge, of } from 'rxjs'; import * as i1$1 from 'novo-elements/services'; import { DateUtil, Helpers, BooleanInput, notify } from 'novo-elements/utils'; import * as i5$1 from 'novo-elements/elements/dropdown'; import { NovoDropdownElement, NovoDropdownModule } from 'novo-elements/elements/dropdown'; import { endOfToday, startOfToday } from 'date-fns'; import { trigger, state, style, transition, animate } from '@angular/animations'; import * as i3 from 'novo-elements/elements/icon'; import { NovoIconModule } from 'novo-elements/elements/icon'; import * as i5 from 'novo-elements/elements/date-picker'; import { NovoDatePickerModule } from 'novo-elements/elements/date-picker'; import * as i6 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i7 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import * as i3$1 from 'novo-elements/elements/button'; import { NovoButtonModule } from 'novo-elements/elements/button'; import * as i11 from 'novo-elements/elements/common'; import { NovoTemplate, NovoOptionModule, NovoCommonModule } from 'novo-elements/elements/common'; import * as i12 from 'novo-elements/elements/tooltip'; import { NovoTooltipModule } from 'novo-elements/elements/tooltip'; import * as i13 from 'novo-elements/elements/flex'; import { NovoFlexModule } from 'novo-elements/elements/flex'; import * as i14 from 'novo-elements/elements/field'; import { NovoFieldModule } from 'novo-elements/elements/field'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { startWith, switchMap, map, catchError } from 'rxjs/operators'; import * as i9$1 from 'novo-elements/elements/drag-drop'; import { NovoDragDropModule } from 'novo-elements/elements/drag-drop'; import * as i6$1 from 'novo-elements/elements/loading'; import { NovoLoadingModule } from 'novo-elements/elements/loading'; import * as i11$1 from 'novo-elements/elements/search'; import { NovoSearchBoxModule } from 'novo-elements/elements/search'; import * as i14$1 from '@angular/cdk/scrolling'; import { ScrollingModule } from '@angular/cdk/scrolling'; import * as i7$1 from 'novo-elements/elements/tiles'; import { NovoTilesModule } from 'novo-elements/elements/tiles'; import * as i9 from 'novo-elements/elements/select'; import { NovoSelectModule } from 'novo-elements/elements/select'; import { NovoCheckboxModule } from 'novo-elements/elements/checkbox'; import { NovoFormExtrasModule } from 'novo-elements/elements/form'; /** * Injection token used to provide the parent component to options. */ const NOVO_DATA_TABLE_REF = new InjectionToken('NOVO_DATA_TABLE_REF'); class NovoDataTableCheckboxHeaderCell extends CdkHeaderCell { get isAtLimit() { return (this.maxSelected && this.dataTable.state.selectedRows.size + this.dataTable.dataSource.data.length > this.maxSelected && !this.checked); } constructor(columnDef, elementRef, renderer, dataTable, ref, toaster) { super(columnDef, elementRef); this.dataTable = dataTable; this.ref = ref; this.toaster = toaster; this.role = 'columnheader'; this.maxSelected = undefined; this.checked = false; renderer.setAttribute(elementRef.nativeElement, 'data-automation-id', `novo-checkbox-column-header-${columnDef.cssClassFriendlyName}`); renderer.addClass(elementRef.nativeElement, `novo-checkbox-column-${columnDef.cssClassFriendlyName}`); renderer.addClass(elementRef.nativeElement, 'novo-data-table-checkbox-header-cell'); this.selectionSubscription = this.dataTable.state.selectionSource.subscribe(() => { this.checked = this.dataTable.allCurrentRowsSelected() || (this.dataTable?.canSelectAll && this.dataTable?.allMatchingSelected); if (this.dataTable?.canSelectAll) { this.selectAllChanged(); } this.ref.markForCheck(); }); this.paginationSubscription = this.dataTable.state.paginationSource.subscribe((event) => { if (event.isPageSizeChange) { this.checked = false; if (this.dataTable?.canSelectAll) { this.selectAllChanged(); } this.dataTable.selectRows(false); this.dataTable.state.checkRetainment('pageSize'); this.dataTable.state.reset(false, true); } else { this.checked = this.dataTable.allCurrentRowsSelected() || (this.dataTable?.canSelectAll && this.dataTable?.allMatchingSelected); if (this.dataTable?.canSelectAll) { this.selectAllChanged(); } } this.ref.markForCheck(); }); this.resetSubscription = this.dataTable.state.resetSource.subscribe(() => { this.checked = false; if (this.dataTable?.canSelectAll) { this.resetAllMatchingSelected(); } this.ref.markForCheck(); }); } ngOnDestroy() { if (this.selectionSubscription) { this.selectionSubscription.unsubscribe(); } if (this.paginationSubscription) { this.paginationSubscription.unsubscribe(); } if (this.resetSubscription) { this.resetSubscription.unsubscribe(); } } onClick() { if (this.isAtLimit) { this.toaster.alert({ theme: 'danger', position: 'fixedTop', message: 'Error, more than 500 items are not able to be selected at one time', icon: 'caution', }); } else { this.dataTable.selectRows(!this.checked); } if (this.dataTable?.canSelectAll) { if (this.checked) { this.resetAllMatchingSelected(); } else { this.selectAllChanged(); } } } resetAllMatchingSelected() { this.dataTable.state?.allMatchingSelectedSource?.next(false); this.dataTable.state?.onSelectionChange(); } selectAllChanged() { const allSelectedEvent = { allSelected: this.checked, selectedCount: this.dataTable?.state?.selected?.length, allMatchingSelected: this.dataTable?.allMatchingSelected, }; this.dataTable.allSelected.emit(allSelectedEvent); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableCheckboxHeaderCell, deps: [{ token: i1.CdkColumnDef }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: NOVO_DATA_TABLE_REF }, { token: i0.ChangeDetectorRef }, { token: i2.NovoToastService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoDataTableCheckboxHeaderCell, isStandalone: false, selector: "novo-data-table-checkbox-header-cell", inputs: { maxSelected: "maxSelected" }, host: { properties: { "attr.role": "this.role" } }, usesInheritance: true, ngImport: i0, template: ` <div class="data-table-checkbox" (click)="onClick()"> <input type="checkbox" [checked]="checked" /> <label> <i [class.bhi-checkbox-empty]="!checked" [class.bhi-checkbox-filled]="checked"></i> </label> </div> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableCheckboxHeaderCell, decorators: [{ type: Component, args: [{ selector: 'novo-data-table-checkbox-header-cell', template: ` <div class="data-table-checkbox" (click)="onClick()"> <input type="checkbox" [checked]="checked" /> <label> <i [class.bhi-checkbox-empty]="!checked" [class.bhi-checkbox-filled]="checked"></i> </label> </div> `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }] }], ctorParameters: () => [{ type: i1.CdkColumnDef }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: undefined, decorators: [{ type: Inject, args: [NOVO_DATA_TABLE_REF] }] }, { type: i0.ChangeDetectorRef }, { type: i2.NovoToastService }], propDecorators: { role: [{ type: HostBinding, args: ['attr.role'] }], maxSelected: [{ type: Input }] } }); class NovoDataTableExpandHeaderCell extends CdkHeaderCell { constructor(columnDef, elementRef, renderer, dataTable, ref) { super(columnDef, elementRef); this.dataTable = dataTable; this.ref = ref; this.role = 'columnheader'; this.expanded = false; renderer.setAttribute(elementRef.nativeElement, 'data-automation-id', `novo-expand-column-header-${columnDef.cssClassFriendlyName}`); renderer.addClass(elementRef.nativeElement, `novo-expand-column-${columnDef.cssClassFriendlyName}`); renderer.addClass(elementRef.nativeElement, 'novo-data-table-expand-header-cell'); this.expandSubscription = this.dataTable.state.expandSource.subscribe(() => { this.expanded = this.dataTable.allCurrentRowsExpanded(); this.ref.markForCheck(); }); } ngOnDestroy() { if (this.expandSubscription) { this.expandSubscription.unsubscribe(); } } expandAll() { this.dataTable.expandRows(!this.expanded); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableExpandHeaderCell, deps: [{ token: i1.CdkColumnDef }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: NOVO_DATA_TABLE_REF }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoDataTableExpandHeaderCell, isStandalone: false, selector: "novo-data-table-expand-header-cell", host: { properties: { "attr.role": "this.role" } }, usesInheritance: true, ngImport: i0, template: ' <i class="bhi-next data-table-icon" novo-data-table-expander="true" (click)="expandAll()" [class.expanded]="expanded"></i> ', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableExpandHeaderCell, decorators: [{ type: Component, args: [{ selector: 'novo-data-table-expand-header-cell', template: ' <i class="bhi-next data-table-icon" novo-data-table-expander="true" (click)="expandAll()" [class.expanded]="expanded"></i> ', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }] }], ctorParameters: () => [{ type: i1.CdkColumnDef }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: undefined, decorators: [{ type: Inject, args: [NOVO_DATA_TABLE_REF] }] }, { type: i0.ChangeDetectorRef }], propDecorators: { role: [{ type: HostBinding, args: ['attr.role'] }] } }); class NovoDataTableFilterUtils { static constructFilter(filter, type, multiSelect) { let actualFilter = filter; if (filter) { if (type && type === 'date') { if (filter.startDate && filter.endDate) { actualFilter = { min: DateUtil.startOfDay(filter.startDate.date), max: DateUtil.startOfDay(DateUtil.addDays(DateUtil.startOfDay(filter.endDate.date), 1)), }; } else { actualFilter = { min: filter.min ? DateUtil.addDays(startOfToday(), filter.min) : startOfToday(), max: filter.max ? DateUtil.addDays(endOfToday(), filter.max) : endOfToday(), }; } } if (multiSelect && Array.isArray(filter)) { actualFilter = filter.map((filterItem) => { if (filterItem && filterItem.hasOwnProperty('value')) { return filterItem.value; } return filterItem; }); } else if (actualFilter && actualFilter.hasOwnProperty('value')) { actualFilter = filter.value; } } return actualFilter; } } class DataTableState { constructor() { this.selectionSource = new Subject(); this.paginationSource = new Subject(); this.sortFilterSource = new Subject(); this.resetSource = new Subject(); this.expandSource = new Subject(); this.allMatchingSelectedSource = new Subject(); this.dataLoaded = new Subject(); this.dataLoadingSource = new Subject(); this.sort = undefined; this.filter = undefined; this.where = undefined; this.page = 0; this.pageSize = undefined; this.globalSearch = undefined; this.selectedRows = new Map(); this.expandedRows = new Set(); this.isForceRefresh = false; this.updates = new EventEmitter(); this.retainSelected = false; this.savedSearchName = undefined; this.displayedColumns = undefined; } get userFiltered() { return !!(this.filter || this.sort || this.globalSearch || this.outsideFilter || this.where); } get userFilteredInternal() { return !!(this.filter || this.sort || this.globalSearch || this.where); } get selected() { return Array.from(this.selectedRows.values()); } reset(fireUpdate = true, persistUserFilters) { this.setState({}, fireUpdate, persistUserFilters); } clearSort(fireUpdate = true) { this.sort = undefined; this.page = 0; this.checkRetainment('sort'); this.reset(fireUpdate, true); this.onSortFilterChange(); if (fireUpdate) { this.updates.emit({ sort: this.sort, filter: this.filter, globalSearch: this.globalSearch, where: this.where, }); } } clearFilter(fireUpdate = true) { this.filter = undefined; this.globalSearch = undefined; this.page = 0; this.checkRetainment('filter'); this.reset(fireUpdate, true); this.onSortFilterChange(); if (fireUpdate) { this.updates.emit({ sort: this.sort, filter: this.filter, globalSearch: this.globalSearch, where: this.where, }); } } clearQuery(fireUpdate = true) { this.where = undefined; this.page = 0; this.checkRetainment('where'); this.reset(fireUpdate, true); this.onSortFilterChange(); if (fireUpdate) { this.updates.emit({ sort: this.sort, filter: this.filter, globalSearch: this.globalSearch, where: this.where, }); } } clearSelected(fireUpdate = true) { this.allMatchingSelectedSource.next(false); this.globalSearch = undefined; this.page = 0; this.reset(fireUpdate, true); this.onSelectionChange(); if (fireUpdate) { this.updates.emit({ sort: this.sort, filter: this.filter, globalSearch: this.globalSearch, where: this.where, }); } } onSelectionChange() { this.selectionSource.next(); } onExpandChange(targetId) { this.expandSource.next(targetId); } onPaginationChange(isPageSizeChange, pageSize) { this.checkRetainment('page'); this.paginationSource.next({ isPageSizeChange, pageSize }); } onSortFilterChange() { this.checkRetainment('sort'); this.checkRetainment('filter'); this.checkRetainment('where'); this.sortFilterSource.next({ sort: this.sort, filter: this.filter, globalSearch: this.globalSearch, where: this.where, savedSearchName: this.savedSearchName, appliedSearchType: this.appliedSearchType, }); } setInitialSortFilter(preferences) { if (preferences) { if (preferences.where) { this.where = preferences.where; } if (preferences.sort) { this.sort = preferences.sort; } if (preferences.filter) { this.filter = this.transformFilters(preferences.filter); } if (preferences.globalSearch) { this.globalSearch = preferences.globalSearch; } if (preferences.savedSearchName) { this.savedSearchName = preferences.savedSearchName; } if (preferences.appliedSearchType) { this.appliedSearchType = preferences.appliedSearchType; } } } setState(preferences, fireUpdate = true, persistUserFilters = false) { if (!persistUserFilters) { this.where = preferences.where; this.sort = preferences.sort; this.filter = preferences.filter ? this.transformFilters(preferences.filter) : undefined; this.globalSearch = preferences.globalSearch; this.savedSearchName = preferences.savedSearchName; if (preferences.displayedColumns?.length) { this.displayedColumns = preferences.displayedColumns; } this.appliedSearchType = preferences.appliedSearchType; } this.page = 0; if (!this.retainSelected) { this.selectedRows.clear(); this.resetSource.next(); } this.onSortFilterChange(); this.retainSelected = false; if (fireUpdate) { this.updates.emit({ sort: this.sort, filter: this.filter, globalSearch: this.globalSearch, where: this.where, savedSearchName: this.savedSearchName, displayedColumns: this.displayedColumns, appliedSearchType: this.appliedSearchType, }); } } checkRetainment(caller, allMatchingSelected = false) { this.retainSelected = this.selectionOptions?.some((option) => option.label === caller) || this.retainSelected || allMatchingSelected; } transformFilters(filters) { const filterArray = Helpers.convertToArray(filters); filterArray.forEach((filter) => { filter.value = filter.selectedOption && filter.type ? NovoDataTableFilterUtils.constructFilter(filter.selectedOption, filter.type) : filter.value; }); return filterArray; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableState, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableState }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableState, decorators: [{ type: Injectable }] }); var SortDirection; (function (SortDirection) { SortDirection["ASC"] = "ascending"; SortDirection["DESC"] = "descending"; SortDirection["NONE"] = "none"; })(SortDirection || (SortDirection = {})); const activeStyle = { opacity: 1, top: 0 }; const inactiveStyle = { opacity: 0 }; /** Animation that moves the sort indicator. */ const sortAscAnim = trigger('sortAsc', [ // ... state(SortDirection.ASC, style(activeStyle)), state(SortDirection.DESC, style(inactiveStyle)), state(SortDirection.NONE, style(inactiveStyle)), transition('* => ascending', [animate('1s')]), transition('ascending => *', [animate('0.5s')]), ]); const sortDescAnim = trigger('sortDesc', [ // ... state(SortDirection.ASC, style(inactiveStyle)), state(SortDirection.DESC, style(activeStyle)), state(SortDirection.NONE, style(inactiveStyle)), transition('* => descending', [animate('1s')]), transition('descending => *', [animate('0.5s')]), ]); const sortNoneAnim = trigger('sortNone', [ // ... state(SortDirection.ASC, style(inactiveStyle)), state(SortDirection.DESC, style(inactiveStyle)), state(SortDirection.NONE, style(activeStyle)), transition('* => none', [animate('1s')]), transition('none => *', [animate('0.5s')]), ]); class NovoDataTableSortButton { get value() { return this._value; } set value(value) { this._value = value; } get isActive() { return this.value !== SortDirection.NONE; } constructor(state, ref, labels) { this.state = state; this.ref = ref; this.labels = labels; this.sortChange = new EventEmitter(); this.SortDirection = SortDirection; this._value = SortDirection.NONE; } changeSort(dir) { this.value = dir; this.sortChange.emit(dir); } clearSort() { this.state.clearSort(); this.sortChange.emit(SortDirection.NONE); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableSortButton, deps: [{ token: DataTableState }, { token: i0.ChangeDetectorRef }, { token: i1$1.NovoLabelService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoDataTableSortButton, isStandalone: false, selector: "novo-sort-button", inputs: { value: "value" }, outputs: { sortChange: "sortChange" }, ngImport: i0, template: "<novo-icon\n class=\"novo-sort-asc-icon\"\n [class.sort-active]=\"isActive\"\n [class.sort-hidden]=\"value !== SortDirection.ASC\"\n [@sortAsc]=\"value\"\n (click)=\"changeSort(SortDirection.DESC)\">arrow-up</novo-icon>\n<novo-icon\n class=\"novo-sort-desc-icon\"\n [class.sort-active]=\"isActive\"\n [class.sort-hidden]=\"value !== SortDirection.DESC\"\n [@sortDesc]=\"value\"\n (click)=\"changeSort(SortDirection.NONE)\">arrow-down</novo-icon>\n<novo-icon\n class=\"novo-sortable-icon\"\n [class.sort-active]=\"isActive\"\n [class.sort-hidden]=\"value !== SortDirection.NONE\"\n [@sortNone]=\"value\"\n (click)=\"changeSort(SortDirection.ASC)\">sortable</novo-icon>", styles: [":host{display:inline-flex;position:relative;width:1.6rem;height:1.6rem;cursor:pointer}:host novo-icon{position:absolute;opacity:0;color:var(--text-muted)}:host novo-icon:hover{color:var(--selection)}:host novo-icon.sort-hidden{pointer-events:none}:host .novo-sort-asc-icon{top:10px;color:var(--selection)}:host .novo-sort-desc-icon{top:-10px;color:var(--selection)}\n"], dependencies: [{ kind: "component", type: i3.NovoIconComponent, selector: "novo-icon", inputs: ["raised", "theme", "shape", "color", "size", "smaller", "larger", "alt", "name"] }], animations: [sortAscAnim, sortDescAnim, sortNoneAnim], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableSortButton, decorators: [{ type: Component, args: [{ selector: 'novo-sort-button', changeDetection: ChangeDetectionStrategy.OnPush, animations: [sortAscAnim, sortDescAnim, sortNoneAnim], standalone: false, template: "<novo-icon\n class=\"novo-sort-asc-icon\"\n [class.sort-active]=\"isActive\"\n [class.sort-hidden]=\"value !== SortDirection.ASC\"\n [@sortAsc]=\"value\"\n (click)=\"changeSort(SortDirection.DESC)\">arrow-up</novo-icon>\n<novo-icon\n class=\"novo-sort-desc-icon\"\n [class.sort-active]=\"isActive\"\n [class.sort-hidden]=\"value !== SortDirection.DESC\"\n [@sortDesc]=\"value\"\n (click)=\"changeSort(SortDirection.NONE)\">arrow-down</novo-icon>\n<novo-icon\n class=\"novo-sortable-icon\"\n [class.sort-active]=\"isActive\"\n [class.sort-hidden]=\"value !== SortDirection.NONE\"\n [@sortNone]=\"value\"\n (click)=\"changeSort(SortDirection.ASC)\">sortable</novo-icon>", styles: [":host{display:inline-flex;position:relative;width:1.6rem;height:1.6rem;cursor:pointer}:host novo-icon{position:absolute;opacity:0;color:var(--text-muted)}:host novo-icon:hover{color:var(--selection)}:host novo-icon.sort-hidden{pointer-events:none}:host .novo-sort-asc-icon{top:10px;color:var(--selection)}:host .novo-sort-desc-icon{top:-10px;color:var(--selection)}\n"] }] }], ctorParameters: () => [{ type: DataTableState }, { type: i0.ChangeDetectorRef }, { type: i1$1.NovoLabelService }], propDecorators: { sortChange: [{ type: Output }], value: [{ type: Input }] } }); class NovoDataTableSortFilter { constructor(state) { this.state = state; } filter(id, type, value, transform, allowMultipleFilters = false, selectedOption) { let filter; if (allowMultipleFilters) { filter = this.resolveMultiFilter(id, type, value, transform, selectedOption); } else { if (!Helpers.isBlank(value)) { filter = { id, type, value, transform, ...(selectedOption && { selectedOption }) }; } else { filter = undefined; } } this.state.filter = filter; this.state.checkRetainment('filter'); this.state.reset(false, true); this.state.updates.next({ filter, sort: this.state.sort }); this.state.onSortFilterChange(); } sort(id, value, transform) { const sort = { id, value, transform }; this.state.sort = sort; this.state.checkRetainment('sort'); this.state.reset(false, true); this.state.updates.next({ sort, filter: this.state.filter }); this.state.onSortFilterChange(); } resolveMultiFilter(id, type, value, transform, selectedOption) { let filter; filter = Helpers.convertToArray(this.state.filter); const filterIndex = filter.findIndex((aFilter) => aFilter && aFilter.id === id); if (filterIndex > -1) { filter.splice(filterIndex, 1); } if (!Helpers.isBlank(value)) { filter = [...filter, { id, type, value, transform, ...(selectedOption && { selectedOption }) }]; } if (filter.length < 1) { filter = undefined; } return filter; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableSortFilter, deps: [{ token: DataTableState }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.19", type: NovoDataTableSortFilter, isStandalone: false, selector: "[novoDataTableSortFilter]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableSortFilter, decorators: [{ type: Directive, args: [{ selector: '[novoDataTableSortFilter]', standalone: false, }] }], ctorParameters: () => [{ type: DataTableState }] }); class NovoDataTableCellFilterHeader { set filter(filter) { this._filter = filter; this.hasFilter = !Helpers.isEmpty(filter); } get filter() { return this._filter; } constructor(changeDetectorRef, labels) { this.changeDetectorRef = changeDetectorRef; this.labels = labels; this.hasFilter = false; this.clearFilter = new EventEmitter(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableCellFilterHeader, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$1.NovoLabelService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoDataTableCellFilterHeader, isStandalone: false, selector: "novo-data-table-cell-filter-header", inputs: { label: "label", filter: "filter" }, outputs: { clearFilter: "clearFilter" }, ngImport: i0, template: ` <div class="header"> <novo-label>{{ label || labels.filters }}</novo-label> <novo-button theme="dialogue" color="negative" size="small" icon="times" (click)="clearFilter.emit()" *ngIf="hasFilter" data-automation-id="novo-data-table-filter-clear"> {{ labels.clear }} </novo-button> </div> `, isInline: true, dependencies: [{ kind: "directive", type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3$1.NovoButtonElement, selector: "novo-button,button[theme]", inputs: ["color", "side", "size", "theme", "loading", "icon", "secondIcon", "disabled"] }, { kind: "component", type: i11.NovoLabel, selector: "novo-label,[novo-label]", inputs: ["id"] }, { kind: "directive", type: i11.ThemeColorDirective, selector: "[theme]", inputs: ["theme"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableCellFilterHeader, decorators: [{ type: Component, args: [{ selector: 'novo-data-table-cell-filter-header', template: ` <div class="header"> <novo-label>{{ label || labels.filters }}</novo-label> <novo-button theme="dialogue" color="negative" size="small" icon="times" (click)="clearFilter.emit()" *ngIf="hasFilter" data-automation-id="novo-data-table-filter-clear"> {{ labels.clear }} </novo-button> </div> `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }] }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$1.NovoLabelService }], propDecorators: { label: [{ type: Input }], filter: [{ type: Input }], clearFilter: [{ type: Output }] } }); class NovoDataTableCellHeader { set column(column) { this._column = column; this.label = column.type === 'action' ? '' : column.label; this.labelIcon = column.labelIcon; this.config = { sortable: !!column.sortable, filterable: !!column.filterable, resizable: !!column.resizable, }; this.resizable = this.config.resizable; const transforms = {}; if (column.filterable && Helpers.isObject(column.filterable)) { this.config.filterConfig = column.filterable; if (!this.config.filterConfig.type) { this.config.filterConfig = { type: 'text' }; } if (column.filterable.transform) { transforms.filter = column.filterable.transform; } } else { this.config.filterConfig = { type: 'text' }; } if (column.sortable && Helpers.isObject(column.sortable)) { if (column.sortable.transform) { transforms.sort = column.sortable.transform; } } if (this.config.filterConfig.type === 'date' && !this.config.filterConfig.options) { this.config.filterConfig.options = this.getDefaultDateFilterOptions(); } this.config.transforms = transforms; } get column() { return this._column; } constructor(changeDetectorRef, labels, state, renderer, elementRef, _sort, _cdkColumnDef) { this.changeDetectorRef = changeDetectorRef; this.labels = labels; this.state = state; this.renderer = renderer; this.elementRef = elementRef; this._sort = _sort; this._cdkColumnDef = _cdkColumnDef; this.allowMultipleFilters = false; this.toggledFilter = new EventEmitter(); this.icon = 'sortable'; this.filterActive = false; this.sortActive = false; this.sortValue = SortDirection.NONE; this.showCustomRange = false; this.multiSelect = false; this.multiSelectedOptions = []; this.multiSelectedOptionIsHidden = []; this.optionFilter = ''; this.error = false; this.subscriptions = []; this._rerenderSubscription = state.updates.subscribe((change) => this.checkSortFilterState(change)); } ngOnInit() { if (this._cdkColumnDef) { this.id = this._cdkColumnDef.name; } this.setupFilterOptions(); this.changeDetectorRef.markForCheck(); } setupFilterOptions() { this.checkSortFilterState({ filter: this.state.filter, sort: this.state.sort }, true); this.multiSelect = this.config.filterConfig && this.config.filterConfig.type ? this.config.filterConfig.type === 'multi-select' : false; if (this.multiSelect) { this.multiSelectedOptions = this.filter ? [...this.filter] : []; } } ngOnDestroy() { this._rerenderSubscription.unsubscribe(); this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } checkSortFilterState(sortFilterState, initialConfig = false) { if (sortFilterState.sort && sortFilterState.sort.id === this.id) { this.icon = `sort-${sortFilterState.sort.value}`; this.sortValue = sortFilterState.sort.value === 'asc' ? SortDirection.ASC : SortDirection.DESC; this.sortActive = true; } else { this.icon = 'sortable'; this.sortValue = SortDirection.NONE; this.sortActive = false; } const tableFilter = Helpers.convertToArray(sortFilterState.filter); const thisFilter = tableFilter.find((filter) => filter && filter.id === this.id); if (thisFilter) { this.filterActive = true; if (initialConfig && thisFilter.type === 'date' && thisFilter.selectedOption) { this.activeDateFilter = thisFilter.selectedOption.label || this.labels.customDateRange; } this.filter = thisFilter.value; } else { this.filterActive = false; this.filter = undefined; this.activeDateFilter = undefined; this.multiSelectedOptions = []; } if (this.defaultSort && this.id === this.defaultSort.id) { this.icon = `sort-${this.defaultSort.value}`; this.sortActive = true; } this.multiSelect = this.config.filterConfig && this.config.filterConfig.type ? this.config.filterConfig.type === 'multi-select' : false; if (this.multiSelect) { this.multiSelectedOptions = this.filter ? [...this.filter] : []; if (this.config.filterConfig.options) { if (typeof this.config.filterConfig.options[0] === 'string') { this.multiSelectedOptionIsHidden = this.config.filterConfig.options.map((option) => ({ option, hidden: false })); } else { this.multiSelectedOptionIsHidden = this.config.filterConfig.options.map((option) => ({ option, hidden: false, })); } } } this.changeDetectorRef.markForCheck(); } isSelected(option, optionsList) { if (optionsList) { const optionValue = option.hasOwnProperty('value') ? option.value : option; const found = optionsList.find((item) => this.optionPresentCheck(item, optionValue)); return found !== undefined; } return false; } toggleSelection(option) { const optionValue = option.hasOwnProperty('value') ? option.value : option; const optionIndex = this.multiSelectedOptions.findIndex((item) => this.optionPresentCheck(item, optionValue)); this.error = false; if (optionIndex > -1) { this.multiSelectedOptions.splice(optionIndex, 1); if (this.optionFilter && !this.getOptionText(option).toLowerCase().startsWith(this.optionFilter.toLowerCase())) { this.multiSelectedOptionIsHidden[this.multiSelectedOptionIsHidden.findIndex((record) => record.option === option)].hidden = true; } } else { this.multiSelectedOptions.push(optionValue); } } optionPresentCheck(item, optionValue) { if (item.hasOwnProperty('value')) { return item.value === optionValue; } else { return item === optionValue; } } cancel() { this.multiSelectedOptions = this.filter ? [...this.filter] : []; this.dropdown.closePanel(); this.clearOptionFilter(); } filterMultiSelect() { if (this.multiSelectedOptions.length === 0 && !this.filter) { this.multiSelectHasVisibleOptions() && this.dropdown ? (this.error = true) : null; } else { this.clearOptionFilter(); const actualFilter = this.multiSelectedOptions.length > 0 ? [...this.multiSelectedOptions] : undefined; this.filterData(actualFilter); this.dropdown.closePanel(); } } multiSelectOptionFilter(optionFilter) { this.multiSelectedOptionIsHidden.forEach((record) => { if (record.option) { record.hidden = !(this.getOptionText(record.option).toLowerCase().startsWith(optionFilter.toLowerCase()) || this.isSelected(record.option, this.multiSelectedOptions)); } }); } multiSelectOptionIsHidden(option) { return this.multiSelectedOptionIsHidden.find((record) => record.option === option).hidden; } multiSelectHasVisibleOptions() { return this.multiSelectedOptionIsHidden.some((record) => !record.hidden); } getOptionText(option) { if (typeof option !== 'object') { return option.toString(); } else { const opt = option; return (opt.label.length > 0 ? opt.label : opt.value).toString(); } } multiSelectOptionFilterHandleKeydown(event) { if (this.multiSelect) { this.error = false; if (this.dropdown.panelOpen && event.key === "Escape" /* Key.Escape */) { // escape should clear text box and close Helpers.swallowEvent(event); this.clearOptionFilter(); this.dropdown.closePanel(); } else if (event.key === "Enter" /* Key.Enter */) { Helpers.swallowEvent(event); this.filterMultiSelect(); } else if ((event.keyCode >= 65 && event.keyCode <= 90) || (event.keyCode >= 96 && event.keyCode <= 105) || (event.keyCode >= 48 && event.keyCode <= 57)) { this.optionFilterInput.nativeElement.focus(); } } } handleEscapeKeydown(event) { if (!this.multiSelect) { this.error = false; this.dropdown.closePanel(); } } clearOptionFilter() { this.error = false; if (this.optionFilter.length > 0) { this.optionFilter = ''; this.multiSelectedOptionIsHidden.forEach((record) => { record.hidden = false; }); } } startResize(mouseDownEvent) { mouseDownEvent.preventDefault(); const minimumWidth = 60 + (this.config.filterable ? 30 : 0) + (this.config.sortable ? 30 : 0); const startingWidth = this.elementRef.nativeElement.getBoundingClientRect().width; const mouseMoveSubscription = fromEvent(window.document, 'mousemove').subscribe((middleMouseEvent) => { const differenceWidth = middleMouseEvent.clientX - mouseDownEvent.clientX; let width = startingWidth + differenceWidth; if (width < minimumWidth) { width = minimumWidth; } this.setWidth(width); }); const mouseUpSubscription = fromEvent(window.document, 'mouseup').subscribe(() => { mouseUpSubscription.unsubscribe(); mouseMoveSubscription.unsubscribe(); this.changeDetectorRef.markForCheck(); }); this.subscriptions.push(mouseMoveSubscription); this.subscriptions.push(mouseUpSubscription); } setWidth(width) { this._column.width = width; this.renderer.setStyle(this.elementRef.nativeElement, 'min-width', `${width}px`); this.renderer.setStyle(this.elementRef.nativeElement, 'max-width', `${width}px`); this.renderer.setStyle(this.elementRef.nativeElement, 'width', `${width}px`); this.changeDetectorRef.markForCheck(); this.resized.next(this._column); } toggleCustomRange(event, value) { Helpers.swallowEvent(event); this.showCustomRange = value; this.changeDetectorRef.markForCheck(); this.dropdown.openPanel(); // Ensures that the panel correctly updates to the dynamic size of the dropdown } clickedFilter(clickEvt) { if ((typeof this.config.filterConfig === 'object') && this.config.filterConfig.type === 'custom' && !this.filterTemplate) { this.toggledFilter.next(this.id); clickEvt.stopImmediatePropagation(); return; } this.focusInput(); if (this.multiSelect && this.dropdown) { this.dropdown._handleKeydown = (event) => { this.multiSelectOptionFilterHandleKeydown(event); }; this.changeDetectorRef.markForCheck(); } } focusInput() { if (this.filterInput?.nativeElement) { setTimeout(() => this.filterInput.nativeElement.focus()); } } sort() { if (this.changeTimeout) { clearTimeout(this.changeTimeout); } this.changeTimeout = setTimeout(() => { this.direction = this.getNextSortDirection(this.direction); this._sort.sort(this.id, this.direction, this.config.transforms.sort); this.changeDetectorRef.markForCheck(); }, 300); } filterData(filter) { let actualFilter = NovoDataTableFilterUtils.constructFilter(filter, this.config.filterConfig.type, this.multiSelect); const selectedOption = this.config.filterConfig.type === 'date' && filter ? filter : undefined; this.activeDateFilter = selectedOption ? selectedOption.label : undefined; if (this.changeTimeout) { clearTimeout(this.changeTimeout); } this.changeTimeout = setTimeout(() => { if (actualFilter === '') { actualFilter = undefined; } this._sort.filter(this.id, this.config.filterConfig.type, actualFilter, this.config.transforms.filter, this.allowMultipleFilters, selectedOption); this.changeDetectorRef.markForCheck(); }, 300); } clearFilter() { this.filter = undefined; this.multiSelectedOptions = []; this.activeDateFilter = undefined; this.filterData(undefined); this.clearOptionFilter(); this.dropdown.closePanel(); } getNextSortDirection(direction) { if (!direction) { return 'asc'; } if (direction === 'asc') { return 'desc'; } return 'asc'; } getDefaultDateFilterOptions() { const opts = [ { label: this.labels.past1Day, min: -1, max: 0 }, { label: this.labels.past7Days, min: -7, max: 0 }, { label: this.labels.past30Days, min: -30, max: 0 }, { label: this.labels.past90Days, min: -90, max: 0 }, { label: this.labels.past1Year, min: -366, max: 0 }, { label: this.labels.next1Day, min: 0, max: 1 }, { label: this.labels.next7Days, min: 0, max: 7 }, { label: this.labels.next30Days, min: 0, max: 30 }, { label: this.labels.next90Days, min: 0, max: 90 }, { label: this.labels.next1Year, min: 0, max: 366 }, ]; return opts; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NovoDataTableCellHeader, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$1.NovoLabelService }, { token: DataTableState }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: NovoDataTableSortFilter, optional: true }, { token: i1.CdkColumnDef, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: NovoDataTableCellHeader, isStandalone: false, selector: "[novo-data-table-cell-config]", inputs: { defaultSort: "defaultSort", allowMultipleFilters: "allowMultipleFilters", resized: "resized", filterTemplate: "filterTemplate", column: ["novo-data-table-cell-config", "column"] }, outputs: { toggledFilter: "toggledFilter" }, host: { listeners: { "keydown": "multiSelectOptionFilterHandleKeydown($event)", "keydown.escape": "handleEscapeKeydown($event)" }, properties: { "class.resizable": "this.resizable" } }, viewQueries: [{ propertyName: "filterInput", first: true, predicate: ["filterInput"], descendants: true }, { propertyName: "dropdown", first: true, predicate: NovoDropdownElement, descendants: true }, { propertyName: "optionFilterInput", first: true, predicate: ["optionFilterInput"], descendants: true }], ngImport: i0, template: ` <i class="bhi-{{ labelIcon }} label-icon" *ngIf="labelIcon" data-automation-id="novo-data-table-header-icon"></i> <label data-automation-id="novo-data-table-label">{{ label }}</label> <div> <novo-sort-button *ngIf="config.sortable" data-automation-id="novo-data-table-sort" tooltipPosition="left" [tooltip]="labels.sort" [attr.data-feature-id]="'novo-data-table-sort-' + this.id" (sortChange)="sort()" [value]="sortValue"></novo-sort-button> <novo-dropdown *ngIf="config.filterable" side="left" parentScrollSelector=".novo-data-table-container" containerClass="data-table-dropdown" data-automation-id="novo-data-table-filter" [multiple]="multiSelect"> <novo-icon dropdownTrigger class="filter-button" [class.filter-active]="filterActive" [tooltip]="labels.filters" tooltipPosition="right" [attr.data-feature-id]="'novo-data-table-filter-' + this.id" (click)="clickedFilter($event)">filter</novo-icon> <ng-container [ngSwitch]="config.filterConfig.type"> <ng-container *ngSwitchCase="'date'" (keydown.escape)="handleEscapeKeydown($event)"> <novo-data-table-cell-filter-header [filter]="filter" (clearFilter)="clearFilter()"></novo-data-table-cell-filter-header> <div class="optgroup-container"> <novo-optgroup> <ng-container *ngIf="!showCustomRange"> <novo-option [class.active]="activeDateFilter === option.label" *ngFor="let option of config.filterConfig.options" (click)="filterData(option)" [attr.data-automation-id]="'novo-data-table-filter-' + option.label"> <span>{{ option.label }}</span> <novo-icon novoSuffix color="positive" *ngIf="activeDateFilter === option.label">check</novo-icon> </novo-option> </ng-container> <novo-option [class.active]="labels.customDateRange === activeDateFilter" (click)="toggleCustomRange($event, true)" *ngIf="config.filterConfig.allowCustomRange && !showCustomRange"> <span>{{ labels.customDateRange }}</span> <novo-icon novoSuffix color="positive" *ngIf="labels.customDateRange === activeDateFilter">check</novo-icon> </novo-option> <novo-option class="calendar-container" *ngIf="showCustomRange" keepOpen> <novo-stack> <div class="back-link" (click)="toggleCustomRange($event, false)"> <i class="bhi-previous"></i> {{ labels