ng-devui
Version:
DevUI components based on Angular
1 lines • 94.2 kB
Source Map (JSON)
{"version":3,"file":"ng-devui-select.mjs","sources":["../../devui/select/before-checkbox-change.pipe.ts","../../devui/select/select.component.ts","../../devui/select/select.component.html","../../devui/select/select.module.ts","../../devui/select/ng-devui-select.ts"],"sourcesContent":["import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'dBeforeCheckboxChangePipe',\n})\nexport class BeforeCheckboxChangePipe implements PipeTransform {\n transform(func: Function, option: any, index: number): any {\n return func ? () => func(index, option, 'checkbox') : undefined;\n }\n}\n","import {\r\n CdkConnectedOverlay,\r\n CdkOverlayOrigin,\r\n ConnectedOverlayPositionChange,\r\n ConnectedPosition,\r\n ScrollStrategy,\r\n ScrollStrategyOptions,\r\n VerticalConnectionPos,\r\n} from '@angular/cdk/overlay';\r\nimport { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';\r\nimport { DOCUMENT } from '@angular/common';\r\nimport {\r\n AfterViewInit,\r\n ChangeDetectionStrategy,\r\n ChangeDetectorRef,\r\n Component,\r\n ContentChild,\r\n ElementRef,\r\n EventEmitter,\r\n HostBinding,\r\n HostListener,\r\n Inject,\r\n Input,\r\n NgZone,\r\n OnChanges,\r\n OnDestroy,\r\n OnInit,\r\n Output,\r\n Renderer2,\r\n SimpleChanges,\r\n TemplateRef,\r\n ViewChild,\r\n forwardRef,\r\n} from '@angular/core';\r\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\r\nimport { I18nInterface, I18nService } from 'ng-devui/i18n';\r\nimport {\r\n AppendToBodyDirection,\r\n AppendToBodyDirectionsConfig,\r\n AppendToBodyScrollStrategyType,\r\n DevConfigService,\r\n WithConfig,\r\n addClassToOrigin,\r\n fadeInOut,\r\n formWithDropDown,\r\n removeClassFromOrigin,\r\n} from 'ng-devui/utils';\r\nimport { WindowRef } from 'ng-devui/window-ref';\r\nimport { differenceBy, isEqual } from 'lodash-es';\r\nimport { BehaviorSubject, Observable, Subscription, fromEvent, of } from 'rxjs';\r\nimport { debounceTime, filter, map, switchMap } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'd-select',\r\n templateUrl: './select.component.html',\r\n styleUrls: [`./select.component.scss`],\r\n exportAs: 'select',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n useExisting: forwardRef(() => SelectComponent),\r\n multi: true,\r\n },\r\n ],\r\n animations: [fadeInOut],\r\n preserveWhitespaces: false,\r\n})\r\nexport class SelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy, OnChanges {\r\n /**\r\n * 【必选】下拉选项资源,支持Array<string>, Array<{key: value}>\r\n */\r\n @Input() options = [];\r\n /**\r\n * 【可选】是否支持过滤搜索\r\n */\r\n @Input() isSearch = false;\r\n /**\r\n * 【可选】是否支持聚焦自动展开下拉\r\n */\r\n @Input() toggleOnFocus = false;\r\n /**\r\n * 【可选】下拉菜单高度,建议使用px作为高度单位\r\n */\r\n @Input() scrollHight = '300px';\r\n /**\r\n * 【可选】下拉高亮css\r\n */\r\n @Input() highlightItemClass = 'active';\r\n /**\r\n * 【当传入资源(options)类型为Array<{key: value},必选】针对传入资源options的每项对应字段做过滤操作\r\n */\r\n @Input() filterKey: string;\r\n /**\r\n * 【当传入资源(options)类型为Array<{key: value},可选】选项与选中值类型不一致时用于指定赋值属性\r\n */\r\n @Input() valueKey: string;\r\n /**\r\n * 【可选】是否支持多选\r\n */\r\n @Input() multiple: boolean;\r\n /**\r\n * 【可选】是否支持全选\r\n */\r\n @Input() isSelectAll = false;\r\n /**\r\n * 【可选】是否可以输入\r\n */\r\n @Input() readonly = true;\r\n /**\r\n * 【可选】下拉选框尺寸\r\n */\r\n @Input() size: '' | 'sm' | 'lg';\r\n /**\r\n * 【可选】是否appendToBody\r\n */\r\n @Input() appendToBody = false;\r\n /**\r\n * 【可选】cdk模式overlay Positions的控制\r\n */\r\n @Input() appendToBodyDirections: Array<AppendToBodyDirection | ConnectedPosition> = ['rightDown', 'leftDown', 'rightUp', 'leftUp'];\r\n @Input() @WithConfig() appendToBodyScrollStrategy: AppendToBodyScrollStrategyType;\r\n /**\r\n * 【可选】cdk模式origin width\r\n */\r\n @Input() width: number;\r\n /**\r\n * 【可选】模板高度\r\n */\r\n @Input() templateItemSize: number; // 模板itemSize,appendToBody为true\r\n /**\r\n * 【可选】是否禁用下拉框\r\n */\r\n @Input() disabled = false;\r\n /**\r\n * 【可选】下拉默认显示文字\r\n */\r\n @Input() placeholder = '';\r\n @Input() searchPlaceholder = '';\r\n /**\r\n * 【可选】搜索函数,当需要自定义下拉选择过滤规则时可以使用\r\n * 请保证返回值有id和option字段,id是确保尤其多选的时候能正确索引对应选项\r\n * 简单实现参考:\r\n * search = (term) => {\r\n * return of(\r\n * [Lily, May, Jorsh, Shiwa, Nanth]\r\n * .map((option, index) => ({id: index, option: option}))\r\n * .filter(item => item.option.name.toLowerCase().indexOf(term.toLowerCase()) !== -1)\r\n * );\r\n * }\r\n */\r\n @Input() searchFn: (term: string) => Observable<Array<{ id: string | number; option: any }>>;\r\n /**\r\n * 【可选】决定选择框文字如何显示,默认显示filterKey字段或者本身的值\r\n */\r\n @Input() valueParser: (item: any) => any;\r\n /**\r\n * 【可选】决定下拉框没项文字如何显示,默认显示filterKey字段或者本身的值\r\n */\r\n @Input() formatter: (item: any) => string;\r\n @Input() direction: 'up' | 'down' | 'auto' = 'down';\r\n @Input() overview: 'border' | 'underlined' = 'border';\r\n /**\r\n * 【可选】是否开启clear功能(是否开启只对单选生效)\r\n */\r\n @Input() allowClear = false;\r\n @Input() color;\r\n /**\r\n * 【可选】启用数据懒加载,默认不启用\r\n */\r\n @Input() enableLazyLoad = false;\r\n /**\r\n * 是否虚拟滚动\r\n */\r\n @Input() virtualScroll;\r\n /**\r\n * 非必填,如传入,会忽略ContentChild\r\n */\r\n @Input() inputItemTemplate: TemplateRef<any>;\r\n @Input() extraConfig: {\r\n labelization?: {\r\n // 选中项显示成标签一样,带有删除按钮可以单个删除\r\n enable: boolean; // 默认值为false\r\n overflow?: 'normal' | 'scroll-y' | 'multiple-line' | string; // 默认值为''\r\n containerMaxHeight?: string; // 默认值1.8em\r\n /**\r\n * @deprecated\r\n */\r\n containnerMaxHeight?: string; // 默认值1.8em\r\n labelMaxWidth?: string; // 默认100%\r\n maxTags?: number; // 可点选最大标签数,超出则省略显示\r\n };\r\n selectedItemWithTemplate?: {\r\n // 单选情况下,显示选项使用了template的情况下,顶部选中的内容是否也以template展示\r\n enable: boolean; // 默认值为false\r\n };\r\n // 多选情况下,用户搜索后按回车默认操作结果的第一个选项,没有结果则关闭下拉列表\r\n enableFocusFirstFilteredOption?: boolean;\r\n [feature: string]: any;\r\n };\r\n /**\r\n * 【当传入资源(options)类型为Array<{key: value},必选】针对传入资源options的每项对应字段做禁用操作的key\r\n */\r\n @Input() optionDisabledKey = '';\r\n /**\r\n * 【当传入资源(options)类型为Array<{key: value},必选】针对传入资源options的每项对应字段禁止变更的key\r\n */\r\n @Input() optionImmutableKey = '';\r\n /**\r\n * 【可选】配置无数据展示\r\n */\r\n @Input() noResultItemTemplate: TemplateRef<any>;\r\n /**\r\n * 【可选】配置多选的时候是否维持原数组排序还是用户选择的顺序排序,默认是用户顺序\r\n */\r\n @Input() keepMultipleOrder: 'origin' | 'user-select' = 'user-select';\r\n @Input() customViewTemplate: TemplateRef<any>;\r\n /**\r\n * customViewTemplate的方向,支持下方和右方\r\n */\r\n @Input() customViewDirection: 'bottom' | 'right' | 'left' | 'top' = 'bottom';\r\n @Input() autoScrollIntoActive = false;\r\n @Input() autoFocus = false;\r\n @Input() notAutoScroll = false; // 自动聚焦的时候,自动滚动到select位置\r\n @Input() loadingTemplateRef: TemplateRef<any>;\r\n @Input() showItemTitle = false;\r\n @Input() @WithConfig() showAnimation = true;\r\n @Input() @WithConfig() styleType = 'default';\r\n @Input() @WithConfig() showGlowStyle = true;\r\n @Input() beforeChange: (index, option, action) => boolean | Promise<boolean> | Observable<boolean>;\r\n /**\r\n * select下拉toggle事件,值为true或false\r\n */\r\n @Output() toggleChange = new EventEmitter<boolean>();\r\n @Output() loadMore = new EventEmitter<any>();\r\n /**\r\n * 输出函数,当选中某个选项项后,将会调用此函数,参数为当前选择项的值。如果需要获取所有选择状态的值,请参考(ngModelChange)方法\r\n */\r\n @Output() valueChange = new EventEmitter<any>();\r\n @ContentChild(TemplateRef) itemTemplate: TemplateRef<any>;\r\n @ViewChild('selectWrapper', { static: true }) selectWrapper: ElementRef;\r\n @ViewChild('selectInput') selectInputElement: ElementRef;\r\n @ViewChild('selectMenu') selectMenuElement: ElementRef;\r\n @ViewChild('selectBox', { static: true }) selectBoxElement: ElementRef;\r\n @ViewChild('selectInputWithTemplate') selectInputWithTemplateElement: ElementRef;\r\n @ViewChild('selectInputWithLabel') selectInputWithLabelElement: ElementRef;\r\n @ViewChild('filterInput') filterInputElement: ElementRef;\r\n @ViewChild('dropdownUl') dropdownUl: ElementRef;\r\n @ViewChild(CdkConnectedOverlay) connectedOverlay: CdkConnectedOverlay;\r\n @ViewChild(CdkVirtualScrollViewport) virtualScrollViewport: CdkVirtualScrollViewport;\r\n @HostBinding('class.devui-glow-style') get hasGlowStyle() {\r\n return this.showGlowStyle;\r\n }\r\n\r\n set isOpen(value) {\r\n this._isOpen = value;\r\n this.toggleChange.emit(value);\r\n this.setDocumentClickListener();\r\n if (this.selectWrapper) {\r\n this.dropDownWidth = this.width ? this.width : this.selectWrapper.nativeElement.offsetWidth;\r\n }\r\n if (value) {\r\n addClassToOrigin(this.selectWrapper);\r\n setTimeout(() => {\r\n this.startAnimation = true;\r\n this.changeDetectorRef.detectChanges();\r\n this.resetScrollTop();\r\n });\r\n } else {\r\n this.resetScrollTop(true);\r\n removeClassFromOrigin(this.selectWrapper);\r\n this.onTouch();\r\n if (this.direction === 'auto') {\r\n this.clearText();\r\n }\r\n }\r\n }\r\n\r\n get isOpen() {\r\n return this._isOpen;\r\n }\r\n\r\n get isClearIconShow() {\r\n const hasValue = this.multiple ? this.multiItems?.length : this.value;\r\n return this.allowClear && !this.disabled && hasValue;\r\n }\r\n\r\n get showMoreTags() {\r\n return this.multiItems.length > (this.extraConfig?.labelization?.maxTags || 40) && this.isSelectAll && this.virtualScroll;\r\n }\r\n\r\n get moreTagsNum() {\r\n return `+${this.multiItems.length - 1}`;\r\n }\r\n\r\n _inputValue: any;\r\n _isOpen = false;\r\n allChecked = false;\r\n halfChecked = false;\r\n isMouseEvent = false;\r\n showLoading = false;\r\n startAnimation = false;\r\n limitMaxHeight = false;\r\n availableOptions = [];\r\n multiItems = [];\r\n value: any;\r\n filter = '';\r\n activeIndex = -1;\r\n selectIndex = -1;\r\n popDirection: 'top' | 'bottom';\r\n menuPosition: VerticalConnectionPos = 'bottom';\r\n i18nCommonText: I18nInterface['common'];\r\n i18nSubscription: Subscription;\r\n document: Document;\r\n dropDownWidth: number;\r\n scrollHeightNum: number;\r\n lastCloseScrollHeight: number;\r\n minBuffer: number;\r\n maxBuffer: number;\r\n scrollStrategy: ScrollStrategy;\r\n cdkConnectedOverlayOrigin: CdkOverlayOrigin;\r\n overlayPositions: Array<ConnectedPosition>;\r\n virtualScrollViewportSizeMightChange = false;\r\n virtualScrollItemSize: any = {\r\n sm: 30,\r\n normal: 36,\r\n lg: 50,\r\n space: 4,\r\n };\r\n // 容器边距,虚拟滚动设置高度需考虑上下边距\r\n CONTAINER_MARGINS = 12;\r\n ANIMATION_DELAY = 300;\r\n\r\n get showSelectAll() {\r\n return this.isSelectAll && this.multiple && this.availableOptions.length > 0;\r\n }\r\n\r\n private sourceSubscription: BehaviorSubject<any>;\r\n private filterSubscription: Subscription;\r\n private resetting = false;\r\n private onChange = (_: any) => null;\r\n private onTouch = () => null;\r\n\r\n constructor(\r\n @Inject(DOCUMENT) private doc: any,\r\n private renderer: Renderer2,\r\n private windowRef: WindowRef,\r\n private changeDetectorRef: ChangeDetectorRef,\r\n private i18n: I18nService,\r\n private ngZone: NgZone,\r\n private el: ElementRef,\r\n private devConfigService: DevConfigService,\r\n private scrollStrategyOption: ScrollStrategyOptions\r\n ) {\r\n this.valueParser = (item) => this.getValue(item, this.filterKey);\r\n this.formatter = (item) => this.getValue(item, this.filterKey);\r\n this.scrollStrategy = this.scrollStrategyOption.reposition();\r\n this.document = this.doc;\r\n }\r\n\r\n ngOnInit(): void {\r\n if (!this.searchFn) {\r\n this.searchFn = (term: any) => {\r\n return of(\r\n (this.options || [])\r\n .map((option, index) => ({ option: option, id: index }))\r\n .filter((item) => this.formatter(item.option).toLowerCase().indexOf(term.toLowerCase()) !== -1)\r\n );\r\n };\r\n }\r\n\r\n // 只有多选的情况isSelectAll为true才有意义\r\n if (!this.multiple) {\r\n this.isSelectAll = false;\r\n }\r\n this.setI18nText();\r\n this.registerFilterChange();\r\n this.setPositions();\r\n }\r\n\r\n ngAfterViewInit() {\r\n if (this.autoFocus && this.selectBoxElement) {\r\n setTimeout(() => {\r\n this.selectBoxElement.nativeElement.focus({\r\n preventScroll: this.notAutoScroll,\r\n });\r\n });\r\n }\r\n // 当d-select添加limit-max-height样式类时为devui-dropdown-menu-wrap容器添加最大高度限制样式,避免当窗口高度小于指定下拉最大高度时,下拉内容超出cdk容器\r\n if (this.el.nativeElement?.className.includes('limit-max-height')) {\r\n this.limitMaxHeight = true;\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {\r\n if (this.sourceSubscription) {\r\n this.sourceSubscription.unsubscribe();\r\n }\r\n if (this.filterSubscription) {\r\n this.filterSubscription.unsubscribe();\r\n }\r\n if (this.i18nSubscription) {\r\n this.i18nSubscription.unsubscribe();\r\n }\r\n this.document.removeEventListener('click', this.onDocumentClick);\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges): void {\r\n const { searchFn, options, appendToBodyDirections, appendToBodyScrollStrategy, disabled } = changes;\r\n const globalScrollStrategy = this.devConfigService.getConfigForApi('appendToBodyScrollStrategy');\r\n if (searchFn || options) {\r\n this.resetSource();\r\n if (this.virtualScroll && this.virtualScrollViewport) {\r\n this.virtualScrollViewportSizeMightChange = true;\r\n this.virtualScrollViewport.checkViewportSize();\r\n }\r\n }\r\n if (appendToBodyDirections) {\r\n this.setPositions();\r\n }\r\n if (this.appendToBodyScrollStrategy && (appendToBodyScrollStrategy || globalScrollStrategy)) {\r\n const func = this.scrollStrategyOption[this.appendToBodyScrollStrategy];\r\n this.scrollStrategy = func();\r\n }\r\n if (disabled && this.isOpen) {\r\n this.toggle();\r\n }\r\n }\r\n\r\n setPositions() {\r\n if (this.appendToBodyDirections && this.appendToBodyDirections.length > 0) {\r\n this.overlayPositions = this.appendToBodyDirections\r\n .map((position) => {\r\n if (typeof position === 'string') {\r\n return AppendToBodyDirectionsConfig[position];\r\n } else {\r\n return position;\r\n }\r\n })\r\n .filter((position) => position !== undefined);\r\n } else {\r\n this.overlayPositions = undefined;\r\n }\r\n }\r\n\r\n setI18nText() {\r\n this.i18nCommonText = this.i18n.getI18nText().common;\r\n this.i18nSubscription = this.i18n.langChange().subscribe((data) => {\r\n this.i18nCommonText = data.common;\r\n });\r\n }\r\n\r\n getVirtualScrollHeight(len, size) {\r\n if (len > 0) {\r\n let height =\r\n (this.templateItemSize || this.virtualScrollItemSize[size || 'normal']) * len + this.virtualScrollItemSize.space * (len - 1);\r\n if (this.isSelectAll && this.multiple) {\r\n height += this.virtualScrollItemSize[size ? size : 'normal'] + this.virtualScrollItemSize.space;\r\n }\r\n const scrollHeight = parseInt(this.scrollHight, 10);\r\n this.scrollHeightNum = height > scrollHeight ? scrollHeight : height;\r\n return `${this.scrollHeightNum + this.CONTAINER_MARGINS * 2}px`;\r\n }\r\n }\r\n\r\n get realVirtualScrollItemSize() {\r\n const itemSize = (this.templateItemSize || this.virtualScrollItemSize[this.size || 'normal']) + this.virtualScrollItemSize.space;\r\n const num = Math.round(this.scrollHeightNum / itemSize) || 10;\r\n this.minBuffer = num * 1.5 * itemSize;\r\n this.maxBuffer = num * 2.5 * itemSize;\r\n return itemSize;\r\n }\r\n\r\n resetSource() {\r\n if (this.sourceSubscription && this.searchFn) {\r\n this.resetting = true;\r\n this.sourceSubscription.next('');\r\n }\r\n }\r\n\r\n registerOnChange(fn: any): void {\r\n this.onChange = fn;\r\n }\r\n\r\n registerOnTouched(fn: any): void {\r\n this.onTouch = fn;\r\n }\r\n\r\n registerFilterChange(): void {\r\n this.sourceSubscription = new BehaviorSubject<any>('');\r\n this.sourceSubscription.pipe(switchMap((term) => this.searchFn(term))).subscribe((options) => {\r\n this.availableOptions = options;\r\n this.setAvailableOptions();\r\n this.setAllChecked();\r\n this.changeDetectorRef.markForCheck();\r\n if (this.appendToBody) {\r\n setTimeout(() => {\r\n if (this.connectedOverlay && this.connectedOverlay.overlayRef) {\r\n this.connectedOverlay.overlayRef.updatePosition();\r\n }\r\n });\r\n }\r\n if (\r\n !this.multiple &&\r\n (!this.value || (this.availableOptions && !this.availableOptions.find((option) => option.option === this.value)))\r\n ) {\r\n this.selectIndex = this.filter && this.availableOptions && this.availableOptions.length > 0 ? 0 : -1;\r\n }\r\n });\r\n\r\n this.sourceSubscription.subscribe((term) => {\r\n if (this.resetting && term === '') {\r\n this.writeValue(this.value);\r\n this.resetting = false;\r\n }\r\n });\r\n\r\n this.searchInputValueChangeEvent();\r\n }\r\n\r\n searchInputValueChangeEvent() {\r\n if (this.isSearch && this.isOpen && this.filterInputElement) {\r\n this.filterInputElement.nativeElement.focus();\r\n if (!this.filterSubscription || this.appendToBody) {\r\n // 避免重复订阅\r\n this.filterSubscription = fromEvent(this.filterInputElement.nativeElement, 'input')\r\n .pipe(\r\n map((e: any) => e.target.value),\r\n filter((term) => !this.disabled && this.searchFn && term.length >= 0),\r\n debounceTime(this.ANIMATION_DELAY)\r\n )\r\n .subscribe((term) => {\r\n this.selectIndex = -1;\r\n return this.sourceSubscription.next(term);\r\n });\r\n }\r\n }\r\n }\r\n\r\n writeValue(value: any): void {\r\n if (this.multiple) {\r\n this.value = value ?? [];\r\n if (this.showMoreTags) {\r\n this.value = this.value || [];\r\n this.value = Array.isArray(this.value) ? this.value : [this.value];\r\n } else {\r\n this.getMultipleSelectedOption();\r\n this.setAllChecked();\r\n }\r\n } else {\r\n this.value = value ?? '';\r\n this.getSingleSelectedOption();\r\n if (this.autoScrollIntoActive) {\r\n this.scrollIntoActive();\r\n }\r\n }\r\n this.writeIntoInput(this.value);\r\n this.changeDetectorRef.markForCheck();\r\n }\r\n\r\n writeIntoInput(value): void {\r\n let valueItem = value;\r\n if (this.valueKey) {\r\n valueItem = this.multiple ? this.multiItems.map((item) => item.option) : this.getOption(this.availableOptions, value, true);\r\n valueItem = valueItem ?? '';\r\n }\r\n this._inputValue = this.multiple ? (valueItem || []).map((option) => this.valueParser(option)).join(', ') : this.valueParser(valueItem);\r\n this.setAvailableOptions();\r\n if (this.showMoreTags) {\r\n this.multiItems = this.availableOptions.filter((item) => item.isChecked);\r\n this.setAllChecked();\r\n }\r\n }\r\n\r\n setAvailableOptions() {\r\n const hasNoValue = this.value === undefined || this.value === '';\r\n if (hasNoValue || !Array.isArray(this.availableOptions)) {\r\n return;\r\n }\r\n let _value = this.value;\r\n if (!this.multiple) {\r\n _value = [_value];\r\n }\r\n this.availableOptions = this.availableOptions.map((item) => ({\r\n isChecked: _value.findIndex((i) => isEqual(i, this.valueKey ? item.option[this.valueKey] : item.option)) > -1,\r\n id: item.id,\r\n option: item.option,\r\n }));\r\n }\r\n\r\n getMultipleSelectedOption() {\r\n this.value = this.value || [];\r\n this.value = Array.isArray(this.value) ? this.value : [this.value];\r\n this.multiItems = this.valueKey\r\n ? this.value.map((value) => this.getOption(this.availableOptions, value)).filter((item) => item)\r\n : this.value.map((option) => ({ option: option, id: this.getOptionIndex(option) }));\r\n }\r\n\r\n getSingleSelectedOption() {\r\n const selectedItem = this.valueKey\r\n ? this.getOption(this.availableOptions, this.value)\r\n : this.availableOptions.find((item) => this.formatter(item.option) === this.formatter(this.value));\r\n this.activeIndex = selectedItem ? selectedItem.id : -1;\r\n this.selectIndex = this.activeIndex >= 0 ? this.activeIndex : -1;\r\n }\r\n\r\n getOption(data, value, hasOption?) {\r\n const hasValue = (value ?? undefined) !== undefined;\r\n const valueItem = this.getValue(value, this.valueKey);\r\n const result = hasValue ? data.find((item) => this.getValue(item.option, this.valueKey) === valueItem) || '' : '';\r\n return hasOption && result ? result.option : result;\r\n }\r\n\r\n getOptionIndex(option) {\r\n return this.options?.length\r\n ? this.options.findIndex((item) => isEqual(item, option))\r\n : this.availableOptions.findIndex((item) => isEqual(item.option, option));\r\n }\r\n\r\n getValue = (item, key) => {\r\n let result = item ?? '';\r\n if (typeof item === 'object') {\r\n result = item[key] ?? '';\r\n }\r\n return String(result);\r\n };\r\n\r\n scrollIntoActive() {\r\n if (this.activeIndex >= 0) {\r\n setTimeout(() => {\r\n const items = this.dropdownUl?.nativeElement.querySelectorAll('.devui-dropdown-item') || [];\r\n if (items[this.activeIndex]) {\r\n items[this.activeIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });\r\n }\r\n }, this.ANIMATION_DELAY);\r\n }\r\n }\r\n\r\n canChange(option, index, action: string) {\r\n let changeResult = Promise.resolve(true);\r\n\r\n if (this.beforeChange) {\r\n const result: any = this.beforeChange(index, option, action);\r\n if (typeof result !== 'undefined') {\r\n if (result.then) {\r\n changeResult = result;\r\n } else if (result.subscribe) {\r\n changeResult = (result as Observable<boolean>).toPromise();\r\n } else {\r\n changeResult = Promise.resolve(result);\r\n }\r\n }\r\n }\r\n\r\n return changeResult;\r\n }\r\n\r\n choose = (option, index, $event?: Event, operate?: string) => {\r\n if ($event) {\r\n $event.preventDefault();\r\n $event.stopPropagation();\r\n }\r\n\r\n if (typeof option === 'object') {\r\n if (Object.keys(option).length === 0 || this.disabled) {\r\n this.isOpen = false;\r\n return;\r\n }\r\n } else {\r\n if (this.disabled) {\r\n this.isOpen = false;\r\n return;\r\n }\r\n }\r\n\r\n if (this.optionDisabledKey && option[this.optionDisabledKey]) {\r\n return;\r\n }\r\n\r\n if (this.optionImmutableKey && option[this.optionImmutableKey]) {\r\n return;\r\n }\r\n\r\n this.canChange(option, index, operate || 'select').then((change) => {\r\n if (!change) {\r\n return;\r\n }\r\n if (this.multiple) {\r\n const indexOfOption = this.multiItems.findIndex((item) => isEqual(item.option, option));\r\n if (indexOfOption === -1) {\r\n this.multiItems.push({ id: index, option });\r\n } else {\r\n this.multiItems.splice(indexOfOption, 1);\r\n }\r\n if (this.keepMultipleOrder === 'origin') {\r\n this.multiItems.sort((a, b) => a.id - b.id);\r\n }\r\n this.value = this.valueKey ? this.multiItems.map((item) => item.option[this.valueKey]) : this.multiItems.map((item) => item.option);\r\n this.setAllChecked();\r\n } else {\r\n this.value = this.valueKey ? option[this.valueKey] : option;\r\n this.activeIndex = index;\r\n this.selectIndex = index;\r\n this.toggle();\r\n }\r\n this.writeIntoInput(this.value);\r\n this.onChange(this.value);\r\n this.valueChange.emit(option);\r\n });\r\n };\r\n\r\n updateCdkConnectedOverlayOrigin() {\r\n if (this.selectWrapper.nativeElement) {\r\n this.cdkConnectedOverlayOrigin = new CdkOverlayOrigin(formWithDropDown(this.selectWrapper) || this.selectWrapper.nativeElement);\r\n }\r\n }\r\n\r\n resetScrollTop(isClose = false) {\r\n const menuDom = this.selectMenuElement?.nativeElement.querySelector('ul.devui-select-list-unstyled.devui-scrollbar');\r\n if (this.enableLazyLoad && menuDom) {\r\n if (isClose) {\r\n this.lastCloseScrollHeight = menuDom.scrollHeight ?? 0;\r\n } else if (menuDom.scrollHeight < this.lastCloseScrollHeight) {\r\n menuDom.scrollTop = 0;\r\n }\r\n }\r\n }\r\n\r\n autoToggle($event) {\r\n $event.preventDefault();\r\n $event.stopPropagation();\r\n if (this.toggleOnFocus && !this.disabled && !this.isOpen && !this.isMouseEvent) {\r\n this.toggle();\r\n }\r\n }\r\n\r\n // mousedown mouseup解决focus与click冲突问题\r\n @HostListener('mousedown', ['$event'])\r\n public setMouseEventTrue(event) {\r\n this.isMouseEvent = true;\r\n }\r\n @HostListener('mouseup', ['$event'])\r\n public setMouseEventFalse(event) {\r\n this.isMouseEvent = false;\r\n }\r\n\r\n toggle() {\r\n if (this.disabled) {\r\n if (this.isOpen) {\r\n this.isOpen = false;\r\n }\r\n return;\r\n }\r\n\r\n if (!this.isOpen) {\r\n this.filter = '';\r\n this.resetSource();\r\n if (!this.appendToBody) {\r\n let direction = '';\r\n switch (this.direction) {\r\n case 'auto':\r\n direction = this.isBottomRectEnough() ? 'bottom' : 'top';\r\n break;\r\n case 'down':\r\n direction = 'bottom';\r\n break;\r\n case 'up':\r\n direction = 'top';\r\n break;\r\n default:\r\n direction = 'bottom';\r\n }\r\n this.popDirection = <any>direction;\r\n } else {\r\n this.updateCdkConnectedOverlayOrigin();\r\n }\r\n } else if (!this.showAnimation) {\r\n this.startAnimation = false;\r\n }\r\n this.isOpen = !this.isOpen;\r\n if (this.virtualScrollViewportSizeMightChange) {\r\n // 解决虚拟滚动更新options长度展开前无法获取正确高度影响\r\n setTimeout(() => {\r\n if (this.virtualScrollViewportSizeMightChange && this.virtualScrollViewport) {\r\n this.virtualScrollViewportSizeMightChange = false;\r\n this.virtualScrollViewport.checkViewportSize();\r\n }\r\n }, 0);\r\n }\r\n if (this.isSearch && this.isOpen) {\r\n // 条件外移减少setTimeout\r\n setTimeout(() => this.searchInputValueChangeEvent(), 100);\r\n }\r\n }\r\n\r\n isBottomRectEnough() {\r\n const selectMenuElement = this.selectMenuElement.nativeElement;\r\n const selectInputElement = this.selectInputElement || this.selectInputWithLabelElement || this.selectInputWithTemplateElement;\r\n const displayStyle = selectMenuElement.style.display || (<any>window).getComputedStyle(selectMenuElement).display;\r\n let tempStyle;\r\n if (displayStyle === 'none') {\r\n // 必要, 否则首次展开必有问题, 如果animationEnd之后设置为none也会有问题\r\n tempStyle = {\r\n visibility: selectMenuElement.style.visibility,\r\n display: selectMenuElement.style.display,\r\n transform: selectMenuElement.style.transform,\r\n };\r\n this.renderer.setStyle(selectMenuElement, 'visibility', 'hidden');\r\n this.renderer.setStyle(selectMenuElement, 'display', 'block');\r\n this.renderer.setStyle(selectMenuElement, 'transform', 'translate(0, -9999)');\r\n }\r\n const elementHeight = selectMenuElement.offsetHeight;\r\n const bottomDistance = this.windowRef.innerHeight - selectInputElement.nativeElement.getBoundingClientRect().bottom;\r\n const isBottomEnough = bottomDistance >= elementHeight;\r\n if (displayStyle === 'none') {\r\n this.renderer.setStyle(selectMenuElement, 'visibility', tempStyle.visibility);\r\n this.renderer.setStyle(selectMenuElement, 'display', tempStyle.display);\r\n this.renderer.setStyle(selectMenuElement, 'transform', tempStyle.transform);\r\n }\r\n return isBottomEnough;\r\n }\r\n\r\n setDocumentClickListener() {\r\n this.ngZone.runOutsideAngular(() => {\r\n if (this.isOpen) {\r\n this.document.addEventListener('click', this.onDocumentClick);\r\n } else {\r\n this.document.removeEventListener('click', this.onDocumentClick);\r\n }\r\n });\r\n }\r\n\r\n onDocumentClick = ($event: Event) => {\r\n if (this.isOpen && !this.selectBoxElement.nativeElement.contains($event.target)) {\r\n this.isOpen = false;\r\n this.selectIndex = this.activeIndex ? this.activeIndex : -1;\r\n this.changeDetectorRef.detectChanges();\r\n }\r\n };\r\n\r\n onEscKeyup($event) {\r\n if (this.isOpen) {\r\n $event.stopPropagation();\r\n }\r\n this.isOpen = false;\r\n }\r\n\r\n handleKeyUpEvent($event) {\r\n if (this.isOpen) {\r\n $event.preventDefault();\r\n $event.stopPropagation();\r\n this.selectIndex = this.selectIndex === 0 || this.selectIndex === -1 ? this.availableOptions.length - 1 : this.selectIndex - 1;\r\n this.scrollToActive();\r\n }\r\n }\r\n\r\n handleKeyDownEvent($event) {\r\n if (this.isOpen) {\r\n $event.preventDefault();\r\n $event.stopPropagation();\r\n this.selectIndex = this.selectIndex === this.availableOptions.length - 1 ? 0 : this.selectIndex + 1;\r\n this.scrollToActive();\r\n }\r\n }\r\n\r\n scrollToActive(): void {\r\n const that = this;\r\n setTimeout(() => {\r\n try {\r\n const selectIndex = that.selectIndex + (that.isSelectAll ? 1 : 0); // 多了个全选会导致问题,index需要加1\r\n const scrollPane: any = that.dropdownUl.nativeElement.children[selectIndex];\r\n if (scrollPane.scrollIntoViewIfNeeded) {\r\n scrollPane.scrollIntoViewIfNeeded(false);\r\n } else {\r\n const containerInfo = that.dropdownUl.nativeElement.getBoundingClientRect();\r\n const elementInfo = scrollPane.getBoundingClientRect();\r\n if (elementInfo.bottom > containerInfo.bottom || elementInfo.top < containerInfo.top) {\r\n scrollPane.scrollIntoView(false);\r\n }\r\n }\r\n } catch (e) {}\r\n });\r\n }\r\n\r\n handleKeyEnterEvent($event, isSearchInput = false) {\r\n if (this.isOpen) {\r\n $event.preventDefault();\r\n $event.stopPropagation();\r\n const item = this.getSelectedItem();\r\n if (item) {\r\n this.choose(item.option, item.id, $event);\r\n } else if (!isSearchInput) {\r\n this.toggle();\r\n }\r\n } else {\r\n this.toggle();\r\n }\r\n }\r\n\r\n getSelectedItem = () => {\r\n return this.extraConfig?.enableFocusFirstFilteredOption && this.multiple && this.availableOptions.length\r\n ? this.availableOptions[0]\r\n : this.availableOptions[this.selectIndex];\r\n };\r\n\r\n removeItem(item, $event) {\r\n this.choose(item.option, item.id, $event, 'remove');\r\n }\r\n\r\n selectAll() {\r\n const mutableOption = this.optionImmutableKey\r\n ? this.availableOptions.filter((item) => !item.option[this.optionImmutableKey])\r\n : this.availableOptions;\r\n const selectedImmutableOption = this.optionImmutableKey ? this.multiItems.filter((item) => item.option[this.optionImmutableKey]) : [];\r\n const hasNotSelected = differenceBy(mutableOption, this.multiItems, 'id');\r\n\r\n if (hasNotSelected.length) {\r\n mutableOption.forEach((item) => {\r\n const indexOfOption = this.multiItems.findIndex((i) => isEqual(i.option, item.option));\r\n if (indexOfOption === -1) {\r\n this.multiItems.push({ id: item.id, option: item.option });\r\n }\r\n });\r\n } else if (mutableOption.length === this.multiItems.length - selectedImmutableOption.length) {\r\n this.multiItems = [...selectedImmutableOption];\r\n } else {\r\n this.multiItems = differenceBy(this.multiItems, mutableOption, 'id');\r\n }\r\n this.value = this.valueKey ? this.multiItems.map((item) => item.option[this.valueKey]) : this.multiItems.map((item) => item.option);\r\n this.writeIntoInput(this.value);\r\n this.setAllChecked();\r\n this.onChange(this.valueKey ? this.multiItems.map((item) => item.option[this.valueKey]) : this.value);\r\n this.valueChange.emit(this.multiItems);\r\n }\r\n\r\n trackByFn(index, item) {\r\n return index;\r\n }\r\n\r\n trackByOptionPointer(index, item) {\r\n return item.option;\r\n }\r\n\r\n loadMoreEvent(event) {\r\n this.showLoading = true;\r\n this.loadMore.emit({ instance: this, event: event });\r\n }\r\n\r\n loadFinish() {\r\n this.showLoading = false;\r\n this.changeDetectorRef.markForCheck();\r\n }\r\n\r\n loadStart() {\r\n this.showLoading = true;\r\n this.changeDetectorRef.markForCheck();\r\n }\r\n\r\n onPositionChange(position: ConnectedOverlayPositionChange) {\r\n this.menuPosition = position.connectionPair.originY;\r\n }\r\n\r\n animationEnd($event) {\r\n if (!this.isOpen && this.selectMenuElement && this.showAnimation) {\r\n const targetElement = this.selectMenuElement.nativeElement;\r\n this.startAnimation = false;\r\n setTimeout(() => {\r\n // 动画会覆盖导致display还是block, 所以要等动画覆盖完\r\n this.renderer.setStyle(targetElement, 'display', 'none');\r\n });\r\n }\r\n }\r\n\r\n setAllChecked() {\r\n this.allChecked = false;\r\n this.halfChecked = false;\r\n if (!this.showSelectAll) {\r\n return;\r\n }\r\n if (!this.multiItems || this.multiItems.length === 0) {\r\n return;\r\n }\r\n // 从当前下拉选项中用 id 排除已选中选项\r\n const result = differenceBy(this.availableOptions, this.multiItems, 'id');\r\n if (result.length === 0) {\r\n this.allChecked = true;\r\n } else if (result.length === this.availableOptions.length) {\r\n this.allChecked = false;\r\n } else {\r\n this.halfChecked = true;\r\n }\r\n }\r\n\r\n public forceSearchNext() {\r\n this.sourceSubscription.next(this.filter);\r\n }\r\n\r\n valueClear($event) {\r\n $event.stopPropagation();\r\n if (this.multiple) {\r\n this.multiItems = [];\r\n this.value = [];\r\n this.setAllChecked();\r\n } else {\r\n this.value = undefined;\r\n }\r\n this.resetStatus();\r\n this.onChange(this.value);\r\n this.valueChange.emit(this.value);\r\n }\r\n\r\n resetStatus() {\r\n this.writeIntoInput(this.value);\r\n if (this.availableOptions && this.availableOptions[this.activeIndex]) {\r\n this.availableOptions[this.activeIndex].isChecked = false;\r\n }\r\n this.activeIndex = -1;\r\n this.selectIndex = -1;\r\n this.changeDetectorRef.markForCheck();\r\n }\r\n\r\n clearText() {\r\n this.filter = '';\r\n this.forceSearchNext();\r\n }\r\n}\r\n","<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<!--下拉模板-->\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<!--虚拟滚动模板-->\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 'min-height': availableOptions?.length === 0 ? '46px' : showSelectAll ? '100px' : '60px'\n }\"\n [itemSize]=\"realVirtualScrollItemSize\"\n [minBufferPx]=\"minBuffer\"\n [maxBufferPx]=\"maxBuffer\"\n class=\"viewport-wrapper devui-select-list-unstyled devui-scrollbar\"\n #dropdownUl\n >\n <ng-template [ngTemplateOutlet]=\"selectAllTpl\"></ng-template>\n <ng-container *cdkVirtualFor=\"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 </cdk-virtual-scroll-viewport>\n</ng