ng-devui
Version:
DevUI components based on Angular
834 lines (832 loc) • 128 kB
JavaScript
import * as i0 from '@angular/core';
import { Pipe, EventEmitter, forwardRef, TemplateRef, Component, ChangeDetectionStrategy, Inject, Input, Output, ContentChild, ViewChild, HostBinding, HostListener, NgModule } from '@angular/core';
import { __decorate, __metadata } from 'tslib';
import * as i4 from '@angular/cdk/overlay';
import { CdkOverlayOrigin, CdkConnectedOverlay, OverlayModule } from '@angular/cdk/overlay';
import * as i7 from '@angular/cdk/scrolling';
import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling';
import * as i5 from '@angular/common';
import { DOCUMENT, CommonModule } from '@angular/common';
import * as i6 from '@angular/forms';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import * as i2 from 'ng-devui/i18n';
import * as i3 from 'ng-devui/utils';
import { addClassToOrigin, removeClassFromOrigin, AppendToBodyDirectionsConfig, formWithDropDown, fadeInOut, WithConfig, LazyLoadModule } from 'ng-devui/utils';
import * as i1 from 'ng-devui/window-ref';
import { WindowRefModule } from 'ng-devui/window-ref';
import { isEqual, differenceBy } from 'lodash-es';
import { of, BehaviorSubject, fromEvent } from 'rxjs';
import { switchMap, map, filter, debounceTime } from 'rxjs/operators';
import * as i8 from 'ng-devui/checkbox';
import { CheckBoxModule } from 'ng-devui/checkbox';
import * as i9 from 'ng-devui/loading';
import { LoadingModule } from 'ng-devui/loading';
import * as i10 from 'ng-devui/tags';
import { TagsModule } from 'ng-devui/tags';
class BeforeCheckboxChangePipe {
transform(func, option, index) {
return func ? () => func(index, option, 'checkbox') : undefined;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BeforeCheckboxChangePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: BeforeCheckboxChangePipe, name: "dBeforeCheckboxChangePipe" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BeforeCheckboxChangePipe, decorators: [{
type: Pipe,
args: [{
name: 'dBeforeCheckboxChangePipe',
}]
}] });
class SelectComponent {
get hasGlowStyle() {
return this.showGlowStyle;
}
set isOpen(value) {
this._isOpen = value;
this.toggleChange.emit(value);
this.setDocumentClickListener();
if (this.selectWrapper) {
this.dropDownWidth = this.width ? this.width : this.selectWrapper.nativeElement.offsetWidth;
}
if (value) {
addClassToOrigin(this.selectWrapper);
setTimeout(() => {
this.startAnimation = true;
this.changeDetectorRef.detectChanges();
this.resetScrollTop();
});
}
else {
this.resetScrollTop(true);
removeClassFromOrigin(this.selectWrapper);
this.onTouch();
if (this.direction === 'auto') {
this.clearText();
}
}
}
get isOpen() {
return this._isOpen;
}
get isClearIconShow() {
const hasValue = this.multiple ? this.multiItems?.length : this.value;
return this.allowClear && !this.disabled && hasValue;
}
get showMoreTags() {
return this.multiItems.length > (this.extraConfig?.labelization?.maxTags || 40) && this.isSelectAll && this.virtualScroll;
}
get moreTagsNum() {
return `+${this.multiItems.length - 1}`;
}
get showSelectAll() {
return this.isSelectAll && this.multiple && this.availableOptions.length > 0;
}
constructor(doc, renderer, windowRef, changeDetectorRef, i18n, ngZone, el, devConfigService, scrollStrategyOption) {
this.doc = doc;
this.renderer = renderer;
this.windowRef = windowRef;
this.changeDetectorRef = changeDetectorRef;
this.i18n = i18n;
this.ngZone = ngZone;
this.el = el;
this.devConfigService = devConfigService;
this.scrollStrategyOption = scrollStrategyOption;
/**
* 【必选】下拉选项资源,支持Array<string>, Array<{key: value}>
*/
this.options = [];
/**
* 【可选】是否支持过滤搜索
*/
this.isSearch = false;
/**
* 【可选】是否支持聚焦自动展开下拉
*/
this.toggleOnFocus = false;
/**
* 【可选】下拉菜单高度,建议使用px作为高度单位
*/
this.scrollHight = '300px';
/**
* 【可选】下拉高亮css
*/
this.highlightItemClass = 'active';
/**
* 【可选】是否支持全选
*/
this.isSelectAll = false;
/**
* 【可选】是否可以输入
*/
this.readonly = true;
/**
* 【可选】是否appendToBody
*/
this.appendToBody = false;
/**
* 【可选】cdk模式overlay Positions的控制
*/
this.appendToBodyDirections = ['rightDown', 'leftDown', 'rightUp', 'leftUp'];
/**
* 【可选】是否禁用下拉框
*/
this.disabled = false;
/**
* 【可选】下拉默认显示文字
*/
this.placeholder = '';
this.searchPlaceholder = '';
this.direction = 'down';
this.overview = 'border';
/**
* 【可选】是否开启clear功能(是否开启只对单选生效)
*/
this.allowClear = false;
/**
* 【可选】启用数据懒加载,默认不启用
*/
this.enableLazyLoad = false;
/**
* 【当传入资源(options)类型为Array<{key: value},必选】针对传入资源options的每项对应字段做禁用操作的key
*/
this.optionDisabledKey = '';
/**
* 【当传入资源(options)类型为Array<{key: value},必选】针对传入资源options的每项对应字段禁止变更的key
*/
this.optionImmutableKey = '';
/**
* 【可选】配置多选的时候是否维持原数组排序还是用户选择的顺序排序,默认是用户顺序
*/
this.keepMultipleOrder = 'user-select';
/**
* customViewTemplate的方向,支持下方和右方
*/
this.customViewDirection = 'bottom';
this.autoScrollIntoActive = false;
this.autoFocus = false;
this.notAutoScroll = false; // 自动聚焦的时候,自动滚动到select位置
this.showItemTitle = false;
this.showAnimation = true;
this.styleType = 'default';
this.showGlowStyle = true;
/**
* select下拉toggle事件,值为true或false
*/
this.toggleChange = new EventEmitter();
this.loadMore = new EventEmitter();
/**
* 输出函数,当选中某个选项项后,将会调用此函数,参数为当前选择项的值。如果需要获取所有选择状态的值,请参考(ngModelChange)方法
*/
this.valueChange = new EventEmitter();
this._isOpen = false;
this.allChecked = false;
this.halfChecked = false;
this.isMouseEvent = false;
this.showLoading = false;
this.startAnimation = false;
this.limitMaxHeight = false;
this.availableOptions = [];
this.multiItems = [];
this.filter = '';
this.activeIndex = -1;
this.selectIndex = -1;
this.menuPosition = 'bottom';
this.virtualScrollViewportSizeMightChange = false;
this.virtualScrollItemSize = {
sm: 30,
normal: 36,
lg: 50,
space: 4,
};
// 容器边距,虚拟滚动设置高度需考虑上下边距
this.CONTAINER_MARGINS = 12;
this.ANIMATION_DELAY = 300;
this.resetting = false;
this.onChange = (_) => null;
this.onTouch = () => null;
this.getValue = (item, key) => {
let result = item ?? '';
if (typeof item === 'object') {
result = item[key] ?? '';
}
return String(result);
};
this.choose = (option, index, $event, operate) => {
if ($event) {
$event.preventDefault();
$event.stopPropagation();
}
if (typeof option === 'object') {
if (Object.keys(option).length === 0 || this.disabled) {
this.isOpen = false;
return;
}
}
else {
if (this.disabled) {
this.isOpen = false;
return;
}
}
if (this.optionDisabledKey && option[this.optionDisabledKey]) {
return;
}
if (this.optionImmutableKey && option[this.optionImmutableKey]) {
return;
}
this.canChange(option, index, operate || 'select').then((change) => {
if (!change) {
return;
}
if (this.multiple) {
const indexOfOption = this.multiItems.findIndex((item) => isEqual(item.option, option));
if (indexOfOption === -1) {
this.multiItems.push({ id: index, option });
}
else {
this.multiItems.splice(indexOfOption, 1);
}
if (this.keepMultipleOrder === 'origin') {
this.multiItems.sort((a, b) => a.id - b.id);
}
this.value = this.valueKey ? this.multiItems.map((item) => item.option[this.valueKey]) : this.multiItems.map((item) => item.option);
this.setAllChecked();
}
else {
this.value = this.valueKey ? option[this.valueKey] : option;
this.activeIndex = index;
this.selectIndex = index;
this.toggle();
}
this.writeIntoInput(this.value);
this.onChange(this.value);
this.valueChange.emit(option);
});
};
this.onDocumentClick = ($event) => {
if (this.isOpen && !this.selectBoxElement.nativeElement.contains($event.target)) {
this.isOpen = false;
this.selectIndex = this.activeIndex ? this.activeIndex : -1;
this.changeDetectorRef.detectChanges();
}
};
this.getSelectedItem = () => {
return this.extraConfig?.enableFocusFirstFilteredOption && this.multiple && this.availableOptions.length
? this.availableOptions[0]
: this.availableOptions[this.selectIndex];
};
this.valueParser = (item) => this.getValue(item, this.filterKey);
this.formatter = (item) => this.getValue(item, this.filterKey);
this.scrollStrategy = this.scrollStrategyOption.reposition();
this.document = this.doc;
}
ngOnInit() {
if (!this.searchFn) {
this.searchFn = (term) => {
return of((this.options || [])
.map((option, index) => ({ option: option, id: index }))
.filter((item) => this.formatter(item.option).toLowerCase().indexOf(term.toLowerCase()) !== -1));
};
}
// 只有多选的情况isSelectAll为true才有意义
if (!this.multiple) {
this.isSelectAll = false;
}
this.setI18nText();
this.registerFilterChange();
this.setPositions();
}
ngAfterViewInit() {
if (this.autoFocus && this.selectBoxElement) {
setTimeout(() => {
this.selectBoxElement.nativeElement.focus({
preventScroll: this.notAutoScroll,
});
});
}
// 当d-select添加limit-max-height样式类时为devui-dropdown-menu-wrap容器添加最大高度限制样式,避免当窗口高度小于指定下拉最大高度时,下拉内容超出cdk容器
if (this.el.nativeElement?.className.includes('limit-max-height')) {
this.limitMaxHeight = true;
}
}
ngOnDestroy() {
if (this.sourceSubscription) {
this.sourceSubscription.unsubscribe();
}
if (this.filterSubscription) {
this.filterSubscription.unsubscribe();
}
if (this.i18nSubscription) {
this.i18nSubscription.unsubscribe();
}
this.document.removeEventListener('click', this.onDocumentClick);
}
ngOnChanges(changes) {
const { searchFn, options, appendToBodyDirections, appendToBodyScrollStrategy, disabled } = changes;
const globalScrollStrategy = this.devConfigService.getConfigForApi('appendToBodyScrollStrategy');
if (searchFn || options) {
this.resetSource();
if (this.virtualScroll && this.virtualScrollViewport) {
this.virtualScrollViewportSizeMightChange = true;
this.virtualScrollViewport.checkViewportSize();
}
}
if (appendToBodyDirections) {
this.setPositions();
}
if (this.appendToBodyScrollStrategy && (appendToBodyScrollStrategy || globalScrollStrategy)) {
const func = this.scrollStrategyOption[this.appendToBodyScrollStrategy];
this.scrollStrategy = func();
}
if (disabled && this.isOpen) {
this.toggle();
}
}
setPositions() {
if (this.appendToBodyDirections && this.appendToBodyDirections.length > 0) {
this.overlayPositions = this.appendToBodyDirections
.map((position) => {
if (typeof position === 'string') {
return AppendToBodyDirectionsConfig[position];
}
else {
return position;
}
})
.filter((position) => position !== undefined);
}
else {
this.overlayPositions = undefined;
}
}
setI18nText() {
this.i18nCommonText = this.i18n.getI18nText().common;
this.i18nSubscription = this.i18n.langChange().subscribe((data) => {
this.i18nCommonText = data.common;
});
}
getVirtualScrollHeight(len, size) {
if (len > 0) {
let height = (this.templateItemSize || this.virtualScrollItemSize[size || 'normal']) * len + this.virtualScrollItemSize.space * (len - 1);
if (this.isSelectAll && this.multiple) {
height += this.virtualScrollItemSize[size ? size : 'normal'] + this.virtualScrollItemSize.space;
}
const scrollHeight = parseInt(this.scrollHight, 10);
this.scrollHeightNum = height > scrollHeight ? scrollHeight : height;
return `${this.scrollHeightNum + this.CONTAINER_MARGINS * 2}px`;
}
}
get realVirtualScrollItemSize() {
const itemSize = (this.templateItemSize || this.virtualScrollItemSize[this.size || 'normal']) + this.virtualScrollItemSize.space;
const num = Math.round(this.scrollHeightNum / itemSize) || 10;
this.minBuffer = num * 1.5 * itemSize;
this.maxBuffer = num * 2.5 * itemSize;
return itemSize;
}
resetSource() {
if (this.sourceSubscription && this.searchFn) {
this.resetting = true;
this.sourceSubscription.next('');
}
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouch = fn;
}
registerFilterChange() {
this.sourceSubscription = new BehaviorSubject('');
this.sourceSubscription.pipe(switchMap((term) => this.searchFn(term))).subscribe((options) => {
this.availableOptions = options;
this.setAvailableOptions();
this.setAllChecked();
this.changeDetectorRef.markForCheck();
if (this.appendToBody) {
setTimeout(() => {
if (this.connectedOverlay && this.connectedOverlay.overlayRef) {
this.connectedOverlay.overlayRef.updatePosition();
}
});
}
if (!this.multiple &&
(!this.value || (this.availableOptions && !this.availableOptions.find((option) => option.option === this.value)))) {
this.selectIndex = this.filter && this.availableOptions && this.availableOptions.length > 0 ? 0 : -1;
}
});
this.sourceSubscription.subscribe((term) => {
if (this.resetting && term === '') {
this.writeValue(this.value);
this.resetting = false;
}
});
this.searchInputValueChangeEvent();
}
searchInputValueChangeEvent() {
if (this.isSearch && this.isOpen && this.filterInputElement) {
this.filterInputElement.nativeElement.focus();
if (!this.filterSubscription || this.appendToBody) {
// 避免重复订阅
this.filterSubscription = fromEvent(this.filterInputElement.nativeElement, 'input')
.pipe(map((e) => e.target.value), filter((term) => !this.disabled && this.searchFn && term.length >= 0), debounceTime(this.ANIMATION_DELAY))
.subscribe((term) => {
this.selectIndex = -1;
return this.sourceSubscription.next(term);
});
}
}
}
writeValue(value) {
if (this.multiple) {
this.value = value ?? [];
if (this.showMoreTags) {
this.value = this.value || [];
this.value = Array.isArray(this.value) ? this.value : [this.value];
}
else {
this.getMultipleSelectedOption();
this.setAllChecked();
}
}
else {
this.value = value ?? '';
this.getSingleSelectedOption();
if (this.autoScrollIntoActive) {
this.scrollIntoActive();
}
}
this.writeIntoInput(this.value);
this.changeDetectorRef.markForCheck();
}
writeIntoInput(value) {
let valueItem = value;
if (this.valueKey) {
valueItem = this.multiple ? this.multiItems.map((item) => item.option) : this.getOption(this.availableOptions, value, true);
valueItem = valueItem ?? '';
}
this._inputValue = this.multiple ? (valueItem || []).map((option) => this.valueParser(option)).join(', ') : this.valueParser(valueItem);
this.setAvailableOptions();
if (this.showMoreTags) {
this.multiItems = this.availableOptions.filter((item) => item.isChecked);
this.setAllChecked();
}
}
setAvailableOptions() {
const hasNoValue = this.value === undefined || this.value === '';
if (hasNoValue || !Array.isArray(this.availableOptions)) {
return;
}
let _value = this.value;
if (!this.multiple) {
_value = [_value];
}
this.availableOptions = this.availableOptions.map((item) => ({
isChecked: _value.findIndex((i) => isEqual(i, this.valueKey ? item.option[this.valueKey] : item.option)) > -1,
id: item.id,
option: item.option,
}));
}
getMultipleSelectedOption() {
this.value = this.value || [];
this.value = Array.isArray(this.value) ? this.value : [this.value];
this.multiItems = this.valueKey
? this.value.map((value) => this.getOption(this.availableOptions, value)).filter((item) => item)
: this.value.map((option) => ({ option: option, id: this.getOptionIndex(option) }));
}
getSingleSelectedOption() {
const selectedItem = this.valueKey
? this.getOption(this.availableOptions, this.value)
: this.availableOptions.find((item) => this.formatter(item.option) === this.formatter(this.value));
this.activeIndex = selectedItem ? selectedItem.id : -1;
this.selectIndex = this.activeIndex >= 0 ? this.activeIndex : -1;
}
getOption(data, value, hasOption) {
const hasValue = (value ?? undefined) !== undefined;
const valueItem = this.getValue(value, this.valueKey);
const result = hasValue ? data.find((item) => this.getValue(item.option, this.valueKey) === valueItem) || '' : '';
return hasOption && result ? result.option : result;
}
getOptionIndex(option) {
return this.options?.length
? this.options.findIndex((item) => isEqual(item, option))
: this.availableOptions.findIndex((item) => isEqual(item.option, option));
}
scrollIntoActive() {
if (this.activeIndex >= 0) {
setTimeout(() => {
const items = this.dropdownUl?.nativeElement.querySelectorAll('.devui-dropdown-item') || [];
if (items[this.activeIndex]) {
items[this.activeIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
}
}, this.ANIMATION_DELAY);
}
}
canChange(option, index, action) {
let changeResult = Promise.resolve(true);
if (this.beforeChange) {
const result = this.beforeChange(index, option, action);
if (typeof result !== 'undefined') {
if (result.then) {
changeResult = result;
}
else if (result.subscribe) {
changeResult = result.toPromise();
}
else {
changeResult = Promise.resolve(result);
}
}
}
return changeResult;
}
updateCdkConnectedOverlayOrigin() {
if (this.selectWrapper.nativeElement) {
this.cdkConnectedOverlayOrigin = new CdkOverlayOrigin(formWithDropDown(this.selectWrapper) || this.selectWrapper.nativeElement);
}
}
resetScrollTop(isClose = false) {
const menuDom = this.selectMenuElement?.nativeElement.querySelector('ul.devui-select-list-unstyled.devui-scrollbar');
if (this.enableLazyLoad && menuDom) {
if (isClose) {
this.lastCloseScrollHeight = menuDom.scrollHeight ?? 0;
}
else if (menuDom.scrollHeight < this.lastCloseScrollHeight) {
menuDom.scrollTop = 0;
}
}
}
autoToggle($event) {
$event.preventDefault();
$event.stopPropagation();
if (this.toggleOnFocus && !this.disabled && !this.isOpen && !this.isMouseEvent) {
this.toggle();
}
}
// mousedown mouseup解决focus与click冲突问题
setMouseEventTrue(event) {
this.isMouseEvent = true;
}
setMouseEventFalse(event) {
this.isMouseEvent = false;
}
toggle() {
if (this.disabled) {
if (this.isOpen) {
this.isOpen = false;
}
return;
}
if (!this.isOpen) {
this.filter = '';
this.resetSource();
if (!this.appendToBody) {
let direction = '';
switch (this.direction) {
case 'auto':
direction = this.isBottomRectEnough() ? 'bottom' : 'top';
break;
case 'down':
direction = 'bottom';
break;
case 'up':
direction = 'top';
break;
default:
direction = 'bottom';
}
this.popDirection = direction;
}
else {
this.updateCdkConnectedOverlayOrigin();
}
}
else if (!this.showAnimation) {
this.startAnimation = false;
}
this.isOpen = !this.isOpen;
if (this.virtualScrollViewportSizeMightChange) {
// 解决虚拟滚动更新options长度展开前无法获取正确高度影响
setTimeout(() => {
if (this.virtualScrollViewportSizeMightChange && this.virtualScrollViewport) {
this.virtualScrollViewportSizeMightChange = false;
this.virtualScrollViewport.checkViewportSize();
}
}, 0);
}
if (this.isSearch && this.isOpen) {
// 条件外移减少setTimeout
setTimeout(() => this.searchInputValueChangeEvent(), 100);
}
}
isBottomRectEnough() {
const selectMenuElement = this.selectMenuElement.nativeElement;
const selectInputElement = this.selectInputElement || this.selectInputWithLabelElement || this.selectInputWithTemplateElement;
const displayStyle = selectMenuElement.style.display || window.getComputedStyle(selectMenuElement).display;
let tempStyle;
if (displayStyle === 'none') {
// 必要, 否则首次展开必有问题, 如果animationEnd之后设置为none也会有问题
tempStyle = {
visibility: selectMenuElement.style.visibility,
display: selectMenuElement.style.display,
transform: selectMenuElement.style.transform,
};
this.renderer.setStyle(selectMenuElement, 'visibility', 'hidden');
this.renderer.setStyle(selectMenuElement, 'display', 'block');
this.renderer.setStyle(selectMenuElement, 'transform', 'translate(0, -9999)');
}
const elementHeight = selectMenuElement.offsetHeight;
const bottomDistance = this.windowRef.innerHeight - selectInputElement.nativeElement.getBoundingClientRect().bottom;
const isBottomEnough = bottomDistance >= elementHeight;
if (displayStyle === 'none') {
this.renderer.setStyle(selectMenuElement, 'visibility', tempStyle.visibility);
this.renderer.setStyle(selectMenuElement, 'display', tempStyle.display);
this.renderer.setStyle(selectMenuElement, 'transform', tempStyle.transform);
}
return isBottomEnough;
}
setDocumentClickListener() {
this.ngZone.runOutsideAngular(() => {
if (this.isOpen) {
this.document.addEventListener('click', this.onDocumentClick);
}
else {
this.document.removeEventListener('click', this.onDocumentClick);
}
});
}
onEscKeyup($event) {
if (this.isOpen) {
$event.stopPropagation();
}
this.isOpen = false;
}
handleKeyUpEvent($event) {
if (this.isOpen) {
$event.preventDefault();
$event.stopPropagation();
this.selectIndex = this.selectIndex === 0 || this.selectIndex === -1 ? this.availableOptions.length - 1 : this.selectIndex - 1;
this.scrollToActive();
}
}
handleKeyDownEvent($event) {
if (this.isOpen) {
$event.preventDefault();
$event.stopPropagation();
this.selectIndex = this.selectIndex === this.availableOptions.length - 1 ? 0 : this.selectIndex + 1;
this.scrollToActive();
}
}
scrollToActive() {
const that = this;
setTimeout(() => {
try {
const selectIndex = that.selectIndex + (that.isSelectAll ? 1 : 0); // 多了个全选会导致问题,index需要加1
const scrollPane = that.dropdownUl.nativeElement.children[selectIndex];
if (scrollPane.scrollIntoViewIfNeeded) {
scrollPane.scrollIntoViewIfNeeded(false);
}
else {
const containerInfo = that.dropdownUl.nativeElement.getBoundingClientRect();
const elementInfo = scrollPane.getBoundingClientRect();
if (elementInfo.bottom > containerInfo.bottom || elementInfo.top < containerInfo.top) {
scrollPane.scrollIntoView(false);
}
}
}
catch (e) { }
});
}
handleKeyEnterEvent($event, isSearchInput = false) {
if (this.isOpen) {
$event.preventDefault();
$event.stopPropagation();
const item = this.getSelectedItem();
if (item) {
this.choose(item.option, item.id, $event);
}
else if (!isSearchInput) {
this.toggle();
}
}
else {
this.toggle();
}
}
removeItem(item, $event) {
this.choose(item.option, item.id, $event, 'remove');
}
selectAll() {
const mutableOption = this.optionImmutableKey
? this.availableOptions.filter((item) => !item.option[this.optionImmutableKey])
: this.availableOptions;
const selectedImmutableOption = this.optionImmutableKey ? this.multiItems.filter((item) => item.option[this.optionImmutableKey]) : [];
const hasNotSelected = differenceBy(mutableOption, this.multiItems, 'id');
if (hasNotSelected.length) {
mutableOption.forEach((item) => {
const indexOfOption = this.multiItems.findIndex((i) => isEqual(i.option, item.option));
if (indexOfOption === -1) {
this.multiItems.push({ id: item.id, option: item.option });
}
});
}
else if (mutableOption.length === this.multiItems.length - selectedImmutableOption.length) {
this.multiItems = [...selectedImmutableOption];
}
else {
this.multiItems = differenceBy(this.multiItems, mutableOption, 'id');
}
this.value = this.valueKey ? this.multiItems.map((item) => item.option[this.valueKey]) : this.multiItems.map((item) => item.option);
this.writeIntoInput(this.value);
this.setAllChecked();
this.onChange(this.valueKey ? this.multiItems.map((item) => item.option[this.valueKey]) : this.value);
this.valueChange.emit(this.multiItems);
}
trackByFn(index, item) {
return index;
}
trackByOptionPointer(index, item) {
return item.option;
}
loadMoreEvent(event) {
this.showLoading = true;
this.loadMore.emit({ instance: this, event: event });
}
loadFinish() {
this.showLoading = false;
this.changeDetectorRef.markForCheck();
}
loadStart() {
this.showLoading = true;
this.changeDetectorRef.markForCheck();
}
onPositionChange(position) {
this.menuPosition = position.connectionPair.originY;
}
animationEnd($event) {
if (!this.isOpen && this.selectMenuElement && this.showAnimation) {
const targetElement = this.selectMenuElement.nativeElement;
this.startAnimation = false;
setTimeout(() => {
// 动画会覆盖导致display还是block, 所以要等动画覆盖完
this.renderer.setStyle(targetElement, 'display', 'none');
});
}
}
setAllChecked() {
this.allChecked = false;
this.halfChecked = false;
if (!this.showSelectAll) {
return;
}
if (!this.multiItems || this.multiItems.length === 0) {
return;
}
// 从当前下拉选项中用 id 排除已选中选项
const result = differenceBy(this.availableOptions, this.multiItems, 'id');
if (result.length === 0) {
this.allChecked = true;
}
else if (result.length === this.availableOptions.length) {
this.allChecked = false;
}
else {
this.halfChecked = true;
}
}
forceSearchNext() {
this.sourceSubscription.next(this.filter);
}
valueClear($event) {
$event.stopPropagation();
if (this.multiple) {
this.multiItems = [];
this.value = [];
this.setAllChecked();
}
else {
this.value = undefined;
}
this.resetStatus();
this.onChange(this.value);
this.valueChange.emit(this.value);
}
resetStatus() {
this.writeIntoInput(this.value);
if (this.availableOptions && this.availableOptions[this.activeIndex]) {
this.availableOptions[this.activeIndex].isChecked = false;
}
this.activeIndex = -1;
this.selectIndex = -1;
this.changeDetectorRef.markForCheck();
}
clearText() {
this.filter = '';
this.forceSearchNext();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SelectComponent, deps: [{ token: DOCUMENT }, { token: i0.Renderer2 }, { token: i1.WindowRef }, { token: i0.ChangeDetectorRef }, { token: i2.I18nService }, { token: i0.NgZone }, { token: i0.ElementRef }, { token: i3.DevConfigService }, { token: i4.ScrollStrategyOptions }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: SelectComponent, selector: "d-select", inputs: { options: "options", isSearch: "isSearch", toggleOnFocus: "toggleOnFocus", scrollHight: "scrollHight", highlightItemClass: "highlightItemClass", filterKey: "filterKey", valueKey: "valueKey", multiple: "multiple", isSelectAll: "isSelectAll", readonly: "readonly", size: "size", appendToBody: "appendToBody", appendToBodyDirections: "appendToBodyDirections", appendToBodyScrollStrategy: "appendToBodyScrollStrategy", width: "width", templateItemSize: "templateItemSize", disabled: "disabled", placeholder: "placeholder", searchPlaceholder: "searchPlaceholder", searchFn: "searchFn", valueParser: "valueParser", formatter: "formatter", direction: "direction", overview: "overview", allowClear: "allowClear", color: "color", enableLazyLoad: "enableLazyLoad", virtualScroll: "virtualScroll", inputItemTemplate: "inputItemTemplate", extraConfig: "extraConfig", optionDisabledKey: "optionDisabledKey", optionImmutableKey: "optionImmutableKey", noResultItemTemplate: "noResultItemTemplate", keepMultipleOrder: "keepMultipleOrder", customViewTemplate: "customViewTemplate", customViewDirection: "customViewDirection", autoScrollIntoActive: "autoScrollIntoActive", autoFocus: "autoFocus", notAutoScroll: "notAutoScroll", loadingTemplateRef: "loadingTemplateRef", showItemTitle: "showItemTitle", showAnimation: "showAnimation", styleType: "styleType", showGlowStyle: "showGlowStyle", beforeChange: "beforeChange" }, outputs: { toggleChange: "toggleChange", loadMore: "loadMore", valueChange: "valueChange" }, host: { listeners: { "mousedown": "setMouseEventTrue($event)", "mouseup": "setMouseEventFalse($event)" }, properties: { "class.devui-glow-style": "this.hasGlowStyle" } }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectComponent),
multi: true,
},
], queries: [{ propertyName: "itemTemplate", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "selectWrapper", first: true, predicate: ["selectWrapper"], descendants: true, static: true }, { propertyName: "selectInputElement", first: true, predicate: ["selectInput"], descendants: true }, { propertyName: "selectMenuElement", first: true, predicate: ["selectMenu"], descendants: true }, { propertyName: "selectBoxElement", first: true, predicate: ["selectBox"], descendants: true, static: true }, { propertyName: "selectInputWithTemplateElement", first: true, predicate: ["selectInputWithTemplate"], descendants: true }, { propertyName: "selectInputWithLabelElement", first: true, predicate: ["selectInputWithLabel"], descendants: true }, { propertyName: "filterInputElement", first: true, predicate: ["filterInput"], descendants: true }, { propertyName: "dropdownUl", first: true, predicate: ["dropdownUl"], descendants: true }, { propertyName: "connectedOverlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "virtualScrollViewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], exportAs: ["select"], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"devui-drop{{ popDirection === 'bottom' ? 'down' : 'up' }} \n {{ size ? 'devui-select-' + size : '' }} \n devui-dropdown-origin\"\n [ngClass]=\"{\n 'devui-select-underlined-border': overview === 'underlined',\n disabled: disabled,\n open: isOpen,\n 'devui-gray-style': overview !== 'underlined' && styleType === 'gray'\n }\"\n #selectWrapper\n>\n <div\n #selectBox\n class=\"devui-form-group devui-has-feedback\"\n (click)=\"toggle()\"\n (focus)=\"autoToggle($event)\"\n [tabindex]=\"0\"\n (keydown.esc)=\"onEscKeyup($event)\"\n (keydown.ArrowUp)=\"handleKeyUpEvent($event)\"\n (keydown.ArrowDown)=\"handleKeyDownEvent($event)\"\n (keydown.enter)=\"handleKeyEnterEvent($event)\"\n >\n <input\n #selectInput\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled\"\n [ngClass]=\"[size ? 'devui-select-input-' + size : '']\"\n type=\"text\"\n class=\"devui-form-control devui-select-input devui-input\"\n [minLength]=\"0\"\n autocomplete=\"off\"\n [tabindex]=\"-1\"\n [readonly]=\"readonly\"\n [value]=\"_inputValue || ''\"\n *ngIf=\"\n !(extraConfig?.labelization?.enable && multiple) &&\n !(!extraConfig?.labelization?.enable && extraConfig?.selectedItemWithTemplate?.enable && (inputItemTemplate || itemTemplate))\n \"\n />\n <div\n #selectInputWithTemplate\n class=\"devui-select-list-wrapper devui-form-control {{ size ? 'devui-select-input-' + size : '' }}\"\n *ngIf=\"extraConfig?.selectedItemWithTemplate?.enable && (inputItemTemplate || itemTemplate) && !extraConfig?.labelization?.enable\"\n >\n <div class=\"devui-select-placeholder\" *ngIf=\"!_inputValue\">\n {{ placeholder }}\n </div>\n <div class=\"devui-select-selected-item-with-template\" *ngIf=\"_inputValue\">\n <ng-template\n [ngTemplateOutlet]=\"inputItemTemplate || itemTemplate\"\n [ngTemplateOutletContext]=\"{\n option: value,\n filterKey: filterKey,\n position: 'result',\n inputValue: _inputValue\n }\"\n >\n </ng-template>\n </div>\n </div>\n\n <div\n #selectInputWithLabel\n class=\"devui-select-list-wrapper devui-form-control {{ size ? 'devui-select-input-' + size : '' }}\"\n *ngIf=\"extraConfig?.labelization?.enable && multiple\"\n >\n <div class=\"devui-select-placeholder\" *ngIf=\"multiItems.length === 0\">\n {{ placeholder }}\n </div>\n <ul\n *ngIf=\"multiItems.length > 0\"\n class=\"devui-select-tag-list devui-scrollbar {{ extraConfig?.labelization?.overflow }}\"\n [style.maxHeight]=\"extraConfig?.labelization?.containerMaxHeight || extraConfig?.labelization?.containnerMaxHeight\"\n >\n <li\n class=\"devui-select-tag-item\"\n *ngFor=\"let item of showMoreTags ? [multiItems[0]] : multiItems; let index = index; trackBy: trackByOptionPointer\"\n [style.maxWidth]=\"extraConfig?.labelization?.labelMaxWidth\"\n [ngClass]=\"{\n disabled: optionDisabledKey && item.option[optionDisabledKey]\n }\"\n >\n <d-tag\n [deletable]=\"!(disabled || (optionDisabledKey && item.option[optionDisabledKey]))\"\n [maxWidth]=\"'100%'\"\n [tag]=\"valueParser(item.option)\"\n (tagDelete)=\"removeItem(item, $event?.event)\"\n ></d-tag>\n </li>\n <li *ngIf=\"showMoreTags\" class=\"devui-select-tag-item\">\n <d-tag [tag]=\"moreTagsNum\"></d-tag>\n </li>\n </ul>\n </div>\n\n <span class=\"devui-form-control-feedback devui-input-group-addon\" [ngClass]=\"{ 'devui-clear-icon-show': isClearIconShow }\">\n <span class=\"devui-select-clear-icon\" (mousedown)=\"$event.preventDefault()\" (click)=\"valueClear($event)\">\n <svg viewBox=\"0 0 16 16\">\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <path\n d=\"M8,1 C11.8659932,1 15,4.13400675 15,8 C15,11.8659932 11.8659932,15 8,15 C4.13400675,15 1,11.8659932 1,8 C1,4.13400675 4.13400675,1 8,1 Z M5.87867966,5.17157288 C5.68341751,4.97631073 5.36683502,4.97631073 5.17157288,5.17157288 C4.99800652,5.34513923 4.97872137,5.61456363 5.11371742,5.80943177 L5.17157288,5.87867966 L7.29218611,7.99929289 L5.17157288,10.1213203 C4.97631073,10.3165825 4.97631073,10.633165 5.17157288,10.8284271 C5.34513923,11.0019935 5.61456363,11.0212786 5.80943177,10.8862826 L5.87867966,10.8284271 L7.99929289,8.70639967 L10.1213203,10.8284271 C10.3165825,11.0236893 10.633165,11.0236893 10.8284271,10.8284271 C11.0019935,10.6548608 11.0212786,10.3854364 10.8862826,10.1905682 L10.8284271,10.1213203 L8.70710678,8 L10.8284271,5.87867966 C11.0236893,5.68341751 11.0236893,5.36683502 10.8284271,5.17157288 C10.6548608,4.99800652 10.3854364,4.97872137 10.1905682,5.11371742 L10.1213203,5.17157288 L8,7.29289322 L5.87867966,5.17157288 Z\"\n fill-rule=\"nonzero\"\n ></path>\n </g>\n </svg>\n </span>\n <span class=\"devui-select-chevron-icon\" [ngClass]=\"{ 'devui-select-chevron-icon-animation': showAnimation }\">\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <path\n d=\"M3.64644661,5.64644661 C3.82001296,5.47288026 4.08943736,5.45359511 4.2843055,5.58859116 L4.35355339,5.64644661 L8,9.293 L11.6464466,5.64644661 C11.820013,5.47288026 12.0894374,5.45359511 12.2843055,5.58859116 L12.3535534,5.64644661 C12.5271197,5.82001296 12.5464049,6.08943736 12.4114088,6.2843055 L12.3535534,6.35355339 L8.35355339,10.3535534 C8.17998704,10.5271197 7.91056264,10.5464049 7.7156945,10.4114088 L7.64644661,10.3535534 L3.64644661,6.35355339 C3.45118446,6.15829124 3.45118446,5.84170876 3.64644661,5.64644661 Z\"\n ></path>\n </g>\n </svg>\n </span>\n </span>\n </div>\n <ng-container *ngIf=\"appendToBody\">\n <ng-template\n cdk-connected-overlay\n [cdkConnectedOverlayOrigin]=\"cdkConnectedOverlayOrigin\"\n [cdkConnectedOverlayWidth]=\"dropDownWidth\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (positionChange)=\"onPositionChange($event)\"\n (backdropClick)=\"isOpen = false\"\n (detach)=\"isOpen && (isOpen = false)\"\n >\n <ng-template [ngTemplateOutlet]=\"selectDropdownTpl\"></ng-template>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"!appendToBody\">\n <ng-template [ngTemplateOutlet]=\"selectDropdownTpl\"></ng-template>\n </ng-container>\n</div>\n\n<!--\u4E0B\u62C9\u6A21\u677F-->\n<ng-template #selectDropdownTpl>\n <div\n #selectMenu\n class=\"devui-dropdown-menu\"\n [ngClass]=\"{\n 'devui-dropdown-menu-cdk': appendToBody,\n 'devui-custom-right': customViewTemplate && customViewDirection === 'right',\n 'devui-custom-left': customViewTemplate && customViewDirection === 'left',\n 'devui-custom-top': customViewTemplate && customViewDirection === 'top',\n 'devui-dropdown-menu-multiple': multiple\n }\"\n [@fadeInOut]=\"isOpen && startAnimation ? (appendToBody ? menuPosition : popDirection) : 'void'\"\n (@fadeInOut.done)=\"animationEnd($event)\"\n [@.disabled]=\"!showAnimation\"\n >\n <ul\n dLoading\n [showLoading]=\"showLoading\"\n positionType=\"relative\"\n [message]=\"i18nCommonText?.loading\"\n [loadingTemplateRef]=\"loadingTemplateRef\"\n [backdrop]=\"true\"\n [class.limit-max-height]=\"limitMaxHeight\"\n class=\"devui-dropdown-menu-wrap {{ size ? 'devui-dropdown-menu-' + size : '' }}\"\n (click)=\"$event.stopPropagation(); $event.preventDefault()\"\n >\n <li [style.display]=\"isSearch ? 'block' : 'none'\">\n <div class=\"devui-form-group devui-has-feedback\" [style.margin]=\"'0'\">\n <input\n #filterInput\n [ngClass]=\"[size ? 'devui-select-input-' + size : '', filter ? 'devui-search-filter' : '']\"\n name=\"select\"\n type=\"text\"\n [(ngModel)]=\"filter\"\n autocomplete=\"off\"\n [placeholder]=\"searchPlaceholder\"\n class=\"devui-form-control devui-select-search devui-select-input\"\n (keydown.ArrowUp)=\"handleKeyUpEvent($event)\"\n (keydown.ArrowDown)=\"handleKeyDownEvent($event)\"\n (keydown.enter)=\"handleKeyEnterEvent($event, true)\"\n (keydown.esc)=\"onEscKeyup($event)\"\n />\n <span class=\"devui-form-control-feedback\">\n <svg\n width=\"14px\"\n height=\"14px\"\n viewBox=\"0 0 14 14\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n class=\"svg-icon-search\"\n style=\"vertical-align: middle\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <g transform=\"translate(-1.000000, -1.000000)\">\n <path\n d=\"M7.30811922,1 C10.7919973,1 13.6162384,3.82424117 13.6162384,7.30811922 C13.6162384,8.72507634 13.149053,10.0329164 12.3603151,11.0860065 L14.7360564,13.4616238 C15.0879812,13.8135487 15.0879812,14.3841316 14.7360564,14.7360564 C14.3841316,15.0879812 13.8135487,15.0879812 13.4616238,14.7360564 L11.0860065,12.3603151 C10.0329164,13.149053 8.72507634,13.6162384 7.30811922,13.6162384 C3.82424117,13.6162384 1,10.7919973 1,7.30811922 C1,3.82424117 3.82424117,1 7.30811922,1 Z M7.31,2.51 C4.6590332,2.51 2.51,4.6590332 2.51,7.31 C2.51,9.9609668 4.6590332,12.11 7.31,12.11 C9.9609668,12.11 12.11,9.9609668 12.11,7.31 C12.11,4.6590332 9.9609668,2.51 7.31,2.51 Z\"\n fill-rule=\"nonzero\"\n ></path>\n </g>\n </g>\n </svg>\n </span>\n <span\n #clearIcon\n class=\"devui-form-control-feedback devui-search-clear\"\n [ngClass]=\"size ? 'devui-search-clear-' + size : ''\"\n (click)=\"clearText()\"\n *ngIf=\"filter\"\n >\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <path\n d=\"M8,1 C11.8659932,1 15,4.13400675 15,8 C15,11.8659932 11.8659932,15 8,15 C4.13400675,15 1,11.8659932 1,8 C1,4.13400675 4.13400675,1 8,1 Z M5.87867966,5.17157288 C5.68341751,4.97631073 5.36683502,4.97631073 5.17157288,5.17157288 C4.99800652,5.34513923 4.97872137,5.61456363 5.11371742,5.80943177 L5.17157288,5.87867966 L7.29218611,7.99929289 L5.17157288,10.1213203 C4.97631073,10.3165825 4.97631073,10.633165 5.17157288,10.8284271 C5.34513923,11.0019935 5.61456363,11.0212786 5.80943177,10.8862826 L5.87867966,10.8284271 L7.99929289,8.70639967 L10.1213203,10.8284271 C10.3165825,11.0236893 10.633165,11.0236893 10.8284271,10.8284271 C11.0019935,10.6548608 11.0212786,10.3854364 10.8862826,10.1905682 L10.8284271,10.1213203 L8.70710678,8 L10.8284271,5.87867966 C11.0236893,5.68341751 11.0236893,5.36683502 10.8284271,5.17157288 C10.6548608,4.99800652 10.3854364,4.97872137 10.1905682,5.11371742 L10.1213203,5.17157288 L8,7.29289322 L5.87867966,5.17157288 Z\"\n fill-rule=\"nonzero\"\n ></path>\n </g>\n </svg>\n </span>\n </div>\n </li>\n <ul\n *ngIf=\"!virtualScroll || enableLazyLoad; else scrollViewTpl\"\n class=\"devui-select-list-unstyled devui-scrollbar\"\n dLazyLoad\n [enableLazyLoad]=\"enableLazyLoad\"\n (loadMore)=\"loadMoreEvent($event)\"\n [ngStyle]=\"{ 'max-height': scrollHight, width: '100%' }\"\n #dropdownUl\n >\n <ng-template [ngTemplateOutlet]=\"selectAllTpl\"></ng-template>\n <ng-container *ngFor=\"let item of availableOptions; let index = index; trackBy: trackByFn\">\n <ng-template [ngTemplateOutlet]=\"dropdownItemTpl\" [ngTemplateOutletContext]=\"{ item: item, index: index }\"></ng-template>\n </ng-container>\n </ul>\n <div *ngIf=\"availableOptions?.length === 0 && !noResultItemTemplate && !this.showLoading\" class=\"devui-no-data-tip\">\n <span>{{ filter ? i18nCommonText?.noRecordsFound : i18nCommonText?.noData }}</span>\n </div>\n <div *ngIf=\"availableOptions?.length === 0 && noResultItemTemplate && !this.showLoading\" class=\"devui-no-data-tip\">\n <ng-template [ngTemplateOutlet]=\"noResultItemTemplate\" [ngTemplateOutletContext]=\"{ options: options }\"></ng-template>\n </div>\n </ul>\n <ng-container *ngIf=\"customViewTemplate\">\n <div class=\"devui-select-custom-area\">\n <ng-template [ngTemplateOutlet]=\"customViewTemplate\" [ngTemplateOutletContext]=\"{ $implicit: this, choose: choose }\"></ng-template>\n </div>\n </ng-container>\n </div>\n</ng-template>\n\n<!--\u865A\u62DF\u6EDA\u52A8\u6A21\u677F-->\n<ng-template #scrollViewTpl>\n <cdk-virtual-scroll-viewport\n *ngIf=\"availableOptions?.length > 0\"\n [ngStyle]=\"{\n height: getVirtualScrollHeight(availableOptions?.length, size),\n width: '100%',\n 'max-height': scrollHight,\n