UNPKG

ng-zorro-antd

Version:

An enterprise-class UI components based on Ant Design and Angular

1 lines 121 kB
{"version":3,"file":"ng-zorro-antd-cascader.mjs","sources":["../../components/cascader/typings.ts","../../components/cascader/utils.ts","../../components/cascader/cascader-option.component.ts","../../components/cascader/cascader-tree.service.ts","../../components/cascader/cascader.service.ts","../../components/cascader/cascader.component.ts","../../components/cascader/cascader.module.ts","../../components/cascader/public-api.ts","../../components/cascader/ng-zorro-antd-cascader.ts"],"sourcesContent":["/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { Observable } from 'rxjs';\n\nimport { NzTreeNode } from 'ng-zorro-antd/core/tree';\nimport { NzSafeAny, NzSizeLDSType } from 'ng-zorro-antd/core/types';\n\nimport { NzCascaderTreeService } from './cascader-tree.service';\n\nexport type NzCascaderExpandTrigger = 'click' | 'hover';\nexport type NzCascaderTriggerType = 'click' | 'hover';\nexport type NzCascaderSize = NzSizeLDSType;\n\nexport type NzCascaderFilter = (searchValue: string, path: NzCascaderOption[]) => boolean;\nexport type NzCascaderSorter = (a: NzCascaderOption[], b: NzCascaderOption[], inputValue: string) => number;\nexport type NzCascaderPlacement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';\n\nexport interface NzCascaderOption {\n value?: NzSafeAny;\n label?: string;\n title?: string;\n disabled?: boolean;\n loading?: boolean;\n isLeaf?: boolean;\n children?: NzCascaderOption[];\n disableCheckbox?: boolean;\n\n [key: string]: NzSafeAny;\n}\n\nexport interface NzShowSearchOptions {\n filter?: NzCascaderFilter;\n sorter?: NzCascaderSorter;\n}\n\nexport function isShowSearchObject(options: NzShowSearchOptions | boolean): options is NzShowSearchOptions {\n return typeof options !== 'boolean';\n}\n\n/**\n * To avoid circular dependency, provide an interface of `NzCascaderComponent`\n * for `NzCascaderService`.\n */\nexport interface NzCascaderComponentAsSource {\n inputValue: string;\n nzShowSearch: NzShowSearchOptions | boolean;\n nzLabelProperty: string;\n nzValueProperty: string;\n nzChangeOnSelect: boolean;\n selectedNodes: NzTreeNode[];\n\n get treeService(): NzCascaderTreeService;\n\n coerceTreeNodes(value: NzSafeAny[]): NzTreeNode[];\n\n updateSelectedNodes(): void;\n\n nzChangeOn?(option: NzCascaderOption, level: number): boolean;\n\n nzLoadData?(node: NzCascaderOption, index: number): PromiseLike<NzSafeAny> | Observable<NzSafeAny>;\n}\n","/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { NzTreeNode } from 'ng-zorro-antd/core/tree';\n\nexport function isChildNode(node: NzTreeNode): boolean {\n return node.isLeaf || !node.children || !node.children.length;\n}\n\nexport function isParentNode(node: NzTreeNode): boolean {\n return !!node.children && !!node.children.length && !node.isLeaf;\n}\n","/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { Direction } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnInit,\n Output,\n TemplateRef,\n ViewEncapsulation,\n booleanAttribute,\n inject,\n numberAttribute\n} from '@angular/core';\n\nimport { NzHighlightModule } from 'ng-zorro-antd/core/highlight';\nimport { NzOutletModule } from 'ng-zorro-antd/core/outlet';\nimport { NzTreeNode } from 'ng-zorro-antd/core/tree';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\n\nimport { NzCascaderOption } from './typings';\n\n@Component({\n selector: '[nz-cascader-option]',\n exportAs: 'nzCascaderOption',\n imports: [NgTemplateOutlet, NzHighlightModule, NzIconModule, NzOutletModule],\n template: `\n @if (checkable) {\n <span\n class=\"ant-cascader-checkbox\"\n [class.ant-cascader-checkbox-checked]=\"checked\"\n [class.ant-cascader-checkbox-indeterminate]=\"halfChecked\"\n [class.ant-cascader-checkbox-disabled]=\"disabled\"\n (click)=\"onCheckboxClick($event)\"\n >\n <span class=\"ant-cascader-checkbox-inner\"></span>\n </span>\n }\n\n @if (optionTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"optionTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: node.origin, index: columnIndex }\"\n />\n } @else {\n <div\n class=\"ant-cascader-menu-item-content\"\n [innerHTML]=\"node.title | nzHighlight: highlightText : 'g' : 'ant-cascader-menu-item-keyword'\"\n ></div>\n }\n\n @if (!node.isLeaf || node.children?.length || node.isLoading) {\n <div class=\"ant-cascader-menu-item-expand-icon\">\n @if (node.isLoading) {\n <nz-icon nzType=\"loading\" />\n } @else {\n <ng-container *nzStringTemplateOutlet=\"expandIcon\">\n <nz-icon [nzType]=\"$any(expandIcon)\" />\n </ng-container>\n }\n </div>\n }\n `,\n host: {\n class: 'ant-cascader-menu-item ant-cascader-menu-item-expanded',\n '[attr.title]': 'node.title',\n '[class.ant-cascader-menu-item-active]': 'activated',\n '[class.ant-cascader-menu-item-expand]': '!node.isLeaf',\n '[class.ant-cascader-menu-item-disabled]': 'node.isDisabled'\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None\n})\nexport class NzCascaderOptionComponent implements OnInit {\n @Input() optionTemplate: TemplateRef<NzCascaderOption> | null = null;\n @Input() node!: NzTreeNode;\n @Input() activated = false;\n @Input() highlightText!: string;\n @Input() nzLabelProperty = 'label';\n @Input({ transform: numberAttribute }) columnIndex!: number;\n @Input() expandIcon: string | TemplateRef<void> = '';\n @Input() dir: Direction = 'ltr';\n @Input({ transform: booleanAttribute }) checkable?: boolean = false;\n\n @Output() readonly check = new EventEmitter<void>();\n\n public readonly nativeElement: HTMLElement = inject(ElementRef).nativeElement;\n\n constructor(private cdr: ChangeDetectorRef) {}\n\n ngOnInit(): void {\n if (this.expandIcon === '' && this.dir === 'rtl') {\n this.expandIcon = 'left';\n } else if (this.expandIcon === '') {\n this.expandIcon = 'right';\n }\n }\n\n get checked(): boolean {\n return this.node.isChecked;\n }\n\n get halfChecked(): boolean {\n return this.node.isHalfChecked;\n }\n\n get disabled(): boolean {\n return this.node.isDisabled || this.node.isDisableCheckbox;\n }\n\n markForCheck(): void {\n this.cdr.markForCheck();\n }\n\n onCheckboxClick(event: MouseEvent): void {\n event.preventDefault();\n event.stopPropagation();\n if (!this.checkable) {\n return;\n }\n this.check.emit();\n }\n}\n","/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { Injectable } from '@angular/core';\n\nimport { NzTreeBaseService, NzTreeNode, NzTreeNodeKey } from 'ng-zorro-antd/core/tree';\nimport { NzSafeAny } from 'ng-zorro-antd/core/types';\nimport { arraysEqual, isNotNil } from 'ng-zorro-antd/core/util';\n\nimport { NzCascaderOption } from './typings';\n\ninterface InternalFieldNames {\n label: string;\n value: string;\n}\n\n@Injectable()\nexport class NzCascaderTreeService extends NzTreeBaseService {\n fieldNames: InternalFieldNames = {\n label: 'label',\n value: 'value'\n };\n\n override treeNodePostProcessor = (node: NzTreeNode): void => {\n node.key = this.getOptionValue(node);\n node.title = this.getOptionLabel(node);\n };\n\n getOptionValue(node: NzTreeNode): NzSafeAny {\n return node.origin[this.fieldNames.value || 'value'];\n }\n\n getOptionLabel(node: NzTreeNode): string {\n return node.origin[this.fieldNames.label || 'label'];\n }\n\n get children(): NzTreeNode[] {\n return this.rootNodes;\n }\n\n set children(value: Array<NzTreeNode | NzSafeAny>) {\n this.rootNodes = value.map(v => (v instanceof NzTreeNode ? v : new NzTreeNode(v, null)));\n }\n\n constructor() {\n super();\n }\n\n /**\n * Map list of nodes to list of option\n */\n toOptions(nodes: NzTreeNode[]): NzCascaderOption[] {\n return nodes.map(node => node.origin);\n }\n\n getAncestorNodeList(node: NzTreeNode | null): NzTreeNode[] {\n if (!node) {\n return [];\n }\n if (node.parentNode) {\n return [...this.getAncestorNodeList(node.parentNode), node];\n }\n return [node];\n }\n\n /**\n * Render by nzCheckedKeys\n * When keys equals null, just render with checkStrictly\n *\n * @param paths\n * @param checkStrictly\n */\n conductCheckPaths(paths: NzTreeNodeKey[][] | null, checkStrictly: boolean): void {\n this.checkedNodeList = [];\n this.halfCheckedNodeList = [];\n const existsPathList: NzTreeNodeKey[][] = [];\n const calc = (nodes: NzTreeNode[]): void => {\n nodes.forEach(node => {\n if (paths === null) {\n // render tree if no default checked keys found\n node.isChecked = !!node.origin.checked;\n } else {\n // if node is in checked path\n const nodePath = this.getAncestorNodeList(node).map(n => this.getOptionValue(n));\n if (paths.some(keys => arraysEqual(nodePath, keys))) {\n node.isChecked = true;\n node.isHalfChecked = false;\n existsPathList.push(nodePath);\n } else {\n node.isChecked = false;\n node.isHalfChecked = false;\n }\n }\n if (node.children.length > 0) {\n calc(node.children);\n }\n });\n };\n calc(this.rootNodes);\n this.refreshCheckState(checkStrictly);\n // handle missing node\n this.handleMissingNodeList(paths, existsPathList);\n }\n\n conductSelectedPaths(paths: NzTreeNodeKey[][]): void {\n this.selectedNodeList.forEach(node => (node.isSelected = false));\n this.selectedNodeList = [];\n const existsPathList: NzTreeNodeKey[][] = [];\n const calc = (nodes: NzTreeNode[]): boolean =>\n nodes.every(node => {\n // if node is in selected path\n const nodePath = this.getAncestorNodeList(node).map(n => this.getOptionValue(n));\n if (paths.some(keys => arraysEqual(nodePath, keys))) {\n node.isSelected = true;\n this.setSelectedNodeList(node);\n existsPathList.push(nodePath);\n return false;\n } else {\n node.isSelected = false;\n }\n if (node.children.length > 0) {\n return calc(node.children);\n }\n return true;\n });\n calc(this.rootNodes);\n this.handleMissingNodeList(paths, existsPathList);\n }\n\n private handleMissingNodeList(paths: NzTreeNodeKey[][] | null, existsPathList: NzTreeNodeKey[][]): void {\n const missingNodeList = this.getMissingNodeList(paths, existsPathList);\n missingNodeList.forEach(node => {\n this.setSelectedNodeList(node);\n });\n }\n\n private getMissingNodeList(paths: NzTreeNodeKey[][] | null, existsPathList: NzTreeNodeKey[][]): NzTreeNode[] {\n if (!paths) {\n return [];\n }\n return paths\n .filter(path => !existsPathList.some(keys => arraysEqual(path, keys)))\n .map(path => this.createMissingNode(path))\n .filter(isNotNil);\n }\n\n private createMissingNode(path: NzTreeNodeKey[]): NzTreeNode | null {\n if (!path?.length) {\n return null;\n }\n\n const createOption = (key: NzTreeNodeKey): NzSafeAny => {\n return {\n [this.fieldNames.value || 'value']: key,\n [this.fieldNames.label || 'label']: key\n };\n };\n\n let node = new NzTreeNode(createOption(path[0]), null, this);\n\n for (let i = 1; i < path.length; i++) {\n const childNode = new NzTreeNode(createOption(path[i]));\n node.addChildren([childNode]);\n node = childNode;\n }\n\n if (this.isMultiple) {\n node.isChecked = true;\n node.isHalfChecked = false;\n } else {\n node.isSelected = true;\n }\n return node;\n }\n}\n","/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, from, Subject } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\n\nimport { NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/core/tree';\nimport { NzSafeAny } from 'ng-zorro-antd/core/types';\nimport { isNotNil } from 'ng-zorro-antd/core/util';\n\nimport { isShowSearchObject, NzCascaderComponentAsSource, NzCascaderFilter, NzCascaderOption } from './typings';\nimport { isChildNode, isParentNode } from './utils';\n\n/**\n * All data is stored and parsed in NzCascaderService.\n */\n@Injectable()\nexport class NzCascaderService implements OnDestroy {\n /** Activated options in each column. */\n activatedNodes: NzTreeNode[] = [];\n\n /** An array to store cascader items arranged in different layers. */\n columns: NzTreeNode[][] = [];\n\n /** If user has entered searching mode. */\n inSearchingMode = false;\n\n values: NzSafeAny[] = [];\n\n /**\n * Emit an event when loading state changes.\n * Emit true if nzOptions is loading by `nzLoadData`.\n */\n readonly $loading = new BehaviorSubject<boolean>(false);\n\n /**\n * Emit an event to notify cascader it needs to redraw because activated or\n * selected options are changed.\n */\n readonly $redraw = new Subject<void>();\n\n /**\n * Emit an event when an option gets selected.\n * Emit true if a leaf options is selected.\n */\n readonly $nodeSelected = new Subject<NzTreeNode | null>();\n\n /**\n * Emit an event to notify cascader it needs to quit searching mode.\n * Only emit when user do select a searching option.\n */\n readonly $quitSearching = new Subject<void>();\n\n /** To hold columns before entering searching mode. */\n private columnSnapshot: NzTreeNode[][] = [[]];\n\n private cascaderComponent!: NzCascaderComponentAsSource;\n\n private searchOptionPathMap = new Map<NzTreeNode, NzCascaderOption[]>();\n\n /** Return cascader options in the first layer. */\n get nzOptions(): NzCascaderOption[] {\n return this.cascaderComponent.treeService.toOptions(this.columns[0] || []);\n }\n\n ngOnDestroy(): void {\n this.$redraw.complete();\n this.$quitSearching.complete();\n this.$nodeSelected.complete();\n this.$loading.complete();\n this.searchOptionPathMap.clear();\n }\n\n /**\n * Bind cascader component so this service could use inputs.\n */\n withComponent(cascaderComponent: NzCascaderComponentAsSource): void {\n this.cascaderComponent = cascaderComponent;\n }\n\n /**\n * Try to set an option as activated.\n *\n * @param node Cascader option node\n * @param columnIndex Of which column this option is in\n * @param performSelect Select\n * @param multiple Multiple mode\n * @param loadingChildren Try to load children asynchronously.\n */\n setNodeActivated(\n node: NzTreeNode,\n columnIndex: number,\n performSelect: boolean = false,\n multiple: boolean = false,\n loadingChildren: boolean = true\n ): void {\n if (node.isDisabled) {\n return;\n }\n\n this.activatedNodes[columnIndex] = node;\n this.trackAncestorActivatedNodes(columnIndex);\n this.dropBehindActivatedNodes(columnIndex);\n\n if (isParentNode(node)) {\n // Parent option that has children.\n this.setColumnData(node.children!, columnIndex + 1);\n } else if (!node.isLeaf && loadingChildren) {\n // Parent option that should try to load children asynchronously.\n this.loadChildren(node, columnIndex);\n } else if (node.isLeaf) {\n // Leaf option.\n this.dropBehindColumns(columnIndex);\n }\n\n // Actually perform selection to make an options not only activated but also selected.\n if (performSelect && node.isSelectable) {\n this.setNodeSelected(node, columnIndex, multiple);\n }\n\n this.$redraw.next();\n }\n\n /**\n * Set an option as selected.\n * @param node\n * @param index\n * @param multiple\n */\n setNodeSelected(node: NzTreeNode, index: number, multiple: boolean = false): void {\n const changeOn = this.cascaderComponent.nzChangeOn;\n const shouldPerformSelection = (o: NzCascaderOption, i: number): boolean =>\n typeof changeOn === 'function' ? changeOn(o, i) : false;\n\n if (\n multiple ||\n node.isLeaf ||\n this.cascaderComponent.nzChangeOnSelect ||\n shouldPerformSelection(node.origin, index)\n ) {\n node.isSelected = true;\n this.cascaderComponent.treeService.setSelectedNodeList(node, multiple);\n this.cascaderComponent.updateSelectedNodes();\n this.$redraw.next();\n this.$nodeSelected.next(node);\n }\n }\n\n setNodeDeactivatedSinceColumn(column: number): void {\n this.dropBehindActivatedNodes(column - 1);\n this.dropBehindColumns(column);\n this.$redraw.next();\n }\n\n /**\n * Set a searching option as selected, finishing up things.\n *\n * @param node\n * @param multiple\n */\n setSearchOptionSelected(node: NzTreeNode, multiple = false): void {\n this.setNodeSelected(node, node.level, multiple);\n\n setTimeout(() => {\n // Reset data and tell UI only to remove input and reset dropdown width style.\n this.$quitSearching.next();\n this.$redraw.next();\n }, 200);\n }\n\n /**\n * Reset node's `title` and `disabled` status and clear `searchOptionPathMap`.\n */\n private clearSearchOptions(): void {\n for (const node of this.searchOptionPathMap.keys()) {\n node.isDisabled = node.origin.disabled || false;\n node.title = this.getOptionLabel(node.origin);\n }\n this.searchOptionPathMap.clear();\n }\n\n /**\n * Filter cascader options to reset `columns`.\n *\n * @param searchValue The string user wants to search.\n */\n prepareSearchOptions(searchValue: string): void {\n const results: NzTreeNode[] = []; // Search results only have one layer.\n const path: NzTreeNode[] = [];\n const defaultFilter: NzCascaderFilter = (i, p) =>\n p.some(o => {\n const label = this.getOptionLabel(o);\n return !!label && label.indexOf(i) !== -1;\n });\n const showSearch = this.cascaderComponent.nzShowSearch;\n const filter = isShowSearchObject(showSearch) && showSearch.filter ? showSearch.filter : defaultFilter;\n const sorter = isShowSearchObject(showSearch) && showSearch.sorter ? showSearch.sorter : null;\n const loopChild = (node: NzTreeNode, forceDisabled = false): void => {\n path.push(node);\n const cPath = this.cascaderComponent.treeService.toOptions(path);\n if (filter(searchValue, cPath)) {\n this.searchOptionPathMap.set(node, cPath);\n node.isDisabled = forceDisabled || node.isDisabled;\n node.title = cPath.map(p => this.getOptionLabel(p)).join(' / ');\n results.push(node);\n }\n path.pop();\n };\n const loopParent = (node: NzTreeNode, forceDisabled = false): void => {\n const disabled = forceDisabled || node.isDisabled;\n path.push(node);\n node.children!.forEach(sNode => {\n if (!sNode.isLeaf) {\n loopParent(sNode, disabled);\n }\n if (sNode.isLeaf || !sNode.children || !sNode.children.length) {\n loopChild(sNode, disabled);\n }\n });\n path.pop();\n };\n\n if (!this.columnSnapshot.length) {\n this.columns = [[]];\n return;\n }\n\n this.columnSnapshot[0].forEach(o => (isChildNode(o) ? loopChild(o) : loopParent(o)));\n\n if (sorter) {\n results.sort((a, b) => sorter(this.searchOptionPathMap.get(a)!, this.searchOptionPathMap.get(b)!, searchValue));\n }\n\n this.columns = [results];\n this.$redraw.next(); // Search results may be empty, so should redraw.\n }\n\n /**\n * Set searching mode by UI. It deals with things not directly related to UI.\n *\n * @param toSearching If this cascader is entering searching mode\n */\n setSearchingMode(toSearching: boolean): void {\n this.inSearchingMode = toSearching;\n\n if (toSearching) {\n this.clearSearchOptions(); // if reset nzOptions when searching, should clear searchOptionPathMap\n this.columnSnapshot = [...this.columns];\n this.activatedNodes = [];\n } else {\n // User quit searching mode without selecting an option.\n this.clearSearchOptions();\n this.activatedNodes = [];\n\n setTimeout(() => {\n this.columns = [...this.columnSnapshot];\n if (this.cascaderComponent.selectedNodes.length) {\n const activatedNode = this.cascaderComponent.selectedNodes[0];\n const columnIndex = activatedNode.level;\n this.activatedNodes[columnIndex] = activatedNode;\n this.trackAncestorActivatedNodes(columnIndex);\n this.trackAncestorColumnData(columnIndex);\n }\n this.$redraw.next();\n });\n }\n\n this.$redraw.next();\n }\n\n /**\n * Clear selected options.\n */\n clear(): void {\n this.values = [];\n this.activatedNodes = [];\n this.dropBehindColumns(0);\n this.$redraw.next();\n this.$nodeSelected.next(null);\n }\n\n getOptionLabel(o: NzCascaderOption): string {\n return o[this.cascaderComponent.nzLabelProperty || 'label'] as string;\n }\n\n getOptionValue(o: NzCascaderOption): NzSafeAny {\n return o[this.cascaderComponent.nzValueProperty || 'value'];\n }\n\n /**\n * Try to insert options into a column.\n *\n * @param nodes Options to insert\n * @param columnIndex Position\n */\n setColumnData(nodes: NzTreeNode[], columnIndex: number): void {\n this.columns[columnIndex] = nodes;\n this.dropBehindColumns(columnIndex);\n }\n\n /**\n * Set all columns data according to activate option's path\n */\n private trackAncestorColumnData(startIndex: number): void {\n const node = this.activatedNodes[startIndex];\n if (!node) {\n return;\n }\n\n this.dropBehindColumns(startIndex);\n for (let i = 0; i < startIndex; i++) {\n this.columns[i + 1] = this.activatedNodes[i].children;\n }\n }\n\n /**\n * Set all ancestor options as activated.\n */\n private trackAncestorActivatedNodes(startIndex: number): void {\n for (let i = startIndex - 1; i >= 0; i--) {\n if (!this.activatedNodes[i]) {\n this.activatedNodes[i] = this.activatedNodes[i + 1].parentNode!;\n }\n }\n }\n\n private dropBehindActivatedNodes(lastReserveIndex: number): void {\n this.activatedNodes = this.activatedNodes.splice(0, lastReserveIndex + 1);\n }\n\n dropBehindColumns(lastReserveIndex: number): void {\n if (lastReserveIndex < this.columns.length - 1) {\n this.columns = this.columns.slice(0, lastReserveIndex + 1);\n }\n }\n\n /**\n * Load children of an option asynchronously.\n */\n loadChildren(node: NzTreeNode | null, columnIndex: number, onLoaded?: (options: NzCascaderOption[]) => void): void {\n const isRoot = columnIndex < 0 || !isNotNil(node);\n const option: NzCascaderOption = node?.origin || {};\n const loadFn = this.cascaderComponent.nzLoadData;\n\n if (loadFn) {\n // If there isn't any option in columns.\n this.$loading.next(isRoot);\n\n if (node) {\n node.isLoading = true;\n }\n\n from(loadFn(option, columnIndex))\n .pipe(\n finalize(() => {\n node && (node.isLoading = false);\n this.$loading.next(false);\n this.$redraw.next();\n })\n )\n .subscribe({\n next: () => {\n if (option.children) {\n if (!isRoot) {\n const nodes = option.children.map(o => new NzTreeNode(o as NzTreeNodeOptions, node));\n node.children = nodes;\n this.setColumnData(nodes, columnIndex + 1);\n } else {\n // If it's root node, we should initialize the tree.\n const nodes = this.cascaderComponent.coerceTreeNodes(option.children);\n this.cascaderComponent.treeService.initTree(nodes);\n this.setColumnData(nodes, 0);\n }\n onLoaded?.(option.children);\n }\n },\n error: () => {\n node && (node.isLeaf = true);\n }\n });\n }\n }\n\n isLoaded(index: number): boolean {\n return !!this.columns[index] && this.columns[index].length > 0;\n }\n}\n","/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { Direction, Directionality } from '@angular/cdk/bidi';\nimport { BACKSPACE, DOWN_ARROW, ENTER, ESCAPE, LEFT_ARROW, RIGHT_ARROW, UP_ARROW } from '@angular/cdk/keycodes';\nimport {\n CdkConnectedOverlay,\n ConnectedOverlayPositionChange,\n ConnectionPositionPair,\n OverlayModule\n} from '@angular/cdk/overlay';\nimport { _getEventTarget } from '@angular/cdk/platform';\nimport { SlicePipe } from '@angular/common';\nimport {\n booleanAttribute,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n computed,\n ElementRef,\n EventEmitter,\n forwardRef,\n HostListener,\n inject,\n Input,\n NgZone,\n numberAttribute,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n Renderer2,\n signal,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewChildren,\n ViewEncapsulation\n} from '@angular/core';\nimport { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { BehaviorSubject, merge, Observable, of } from 'rxjs';\nimport { distinctUntilChanged, map, startWith, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';\n\nimport { slideMotion } from 'ng-zorro-antd/core/animation';\nimport { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';\nimport { NzFormItemFeedbackIconComponent, NzFormNoStatusService, NzFormStatusService } from 'ng-zorro-antd/core/form';\nimport { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation';\nimport {\n DEFAULT_CASCADER_POSITIONS,\n getPlacementName,\n NzOverlayModule,\n POSITION_MAP,\n POSITION_TYPE\n} from 'ng-zorro-antd/core/overlay';\nimport { NzDestroyService } from 'ng-zorro-antd/core/services';\nimport { NzTreeBase, NzTreeNode } from 'ng-zorro-antd/core/tree';\nimport {\n NgClassInterface,\n NgStyleInterface,\n NzSafeAny,\n NzSizeLDSType,\n NzStatus,\n NzValidateStatus\n} from 'ng-zorro-antd/core/types';\nimport { fromEventOutsideAngular, getStatusClassNames, isNotNil, toArray } from 'ng-zorro-antd/core/util';\nimport { NzEmptyModule } from 'ng-zorro-antd/empty';\nimport { NzCascaderI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n';\nimport { NzIconModule } from 'ng-zorro-antd/icon';\nimport {\n NzSelectClearComponent,\n NzSelectItemComponent,\n NzSelectPlaceholderComponent,\n NzSelectPlacementType,\n NzSelectSearchComponent\n} from 'ng-zorro-antd/select';\nimport { NZ_SPACE_COMPACT_ITEM_TYPE, NZ_SPACE_COMPACT_SIZE, NzSpaceCompactItemDirective } from 'ng-zorro-antd/space';\n\nimport { NzCascaderOptionComponent } from './cascader-option.component';\nimport { NzCascaderTreeService } from './cascader-tree.service';\nimport { NzCascaderService } from './cascader.service';\nimport {\n NzCascaderComponentAsSource,\n NzCascaderExpandTrigger,\n NzCascaderOption,\n NzCascaderPlacement,\n NzCascaderSize,\n NzCascaderTriggerType,\n NzShowSearchOptions\n} from './typings';\n\nconst NZ_CONFIG_MODULE_NAME: NzConfigKey = 'cascader';\nconst defaultDisplayRender = (labels: string[]): string => labels.join(' / ');\n\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n selector: 'nz-cascader, [nz-cascader]',\n exportAs: 'nzCascader',\n preserveWhitespaces: false,\n template: `\n @if (nzShowInput) {\n <div #selectContainer class=\"ant-select-selector\">\n @if (nzMultiple) {\n @for (node of selectedNodes | slice: 0 : nzMaxTagCount; track node) {\n <nz-select-item\n [deletable]=\"true\"\n [disabled]=\"nzDisabled\"\n [label]=\"nzDisplayWith(getAncestorOptionList(node))\"\n (delete)=\"removeSelected(node)\"\n ></nz-select-item>\n }\n @if (selectedNodes.length > nzMaxTagCount) {\n <nz-select-item\n [deletable]=\"false\"\n [disabled]=\"false\"\n [label]=\"'+ ' + (selectedNodes.length - nzMaxTagCount) + ' ...'\"\n ></nz-select-item>\n }\n }\n\n <nz-select-search\n [showInput]=\"!!nzShowSearch\"\n (isComposingChange)=\"isComposingChange($event)\"\n [value]=\"inputValue\"\n (valueChange)=\"inputValue = $event\"\n [mirrorSync]=\"nzMultiple\"\n [disabled]=\"nzDisabled\"\n [autofocus]=\"nzAutoFocus\"\n [focusTrigger]=\"menuVisible\"\n ></nz-select-search>\n\n @if (showPlaceholder) {\n <nz-select-placeholder\n [placeholder]=\"nzPlaceHolder || locale?.placeholder!\"\n [style.display]=\"inputValue || isComposing ? 'none' : 'block'\"\n ></nz-select-placeholder>\n }\n\n @if (showLabelRender) {\n <nz-select-item\n [deletable]=\"false\"\n [disabled]=\"nzDisabled\"\n [label]=\"labelRenderText\"\n [contentTemplateOutlet]=\"isLabelRenderTemplate ? nzLabelRender : null\"\n [contentTemplateOutletContext]=\"labelRenderContext\"\n ></nz-select-item>\n }\n </div>\n\n @if (nzShowArrow) {\n <span class=\"ant-select-arrow\" [class.ant-select-arrow-loading]=\"isLoading\">\n @if (!isLoading) {\n <nz-icon [nzType]=\"$any(nzSuffixIcon)\" [class.ant-cascader-picker-arrow-expand]=\"menuVisible\" />\n } @else {\n <nz-icon nzType=\"loading\" />\n }\n\n @if (hasFeedback && !!status) {\n <nz-form-item-feedback-icon [status]=\"status\" />\n }\n </span>\n }\n @if (clearIconVisible) {\n <nz-select-clear (clear)=\"clearSelection($event)\"></nz-select-clear>\n }\n }\n <ng-content></ng-content>\n\n <ng-template\n cdkConnectedOverlay\n nzConnectedOverlay\n [cdkConnectedOverlayHasBackdrop]=\"nzBackdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayTransformOriginOn]=\"'.ant-cascader-dropdown'\"\n [cdkConnectedOverlayOpen]=\"menuVisible\"\n (overlayOutsideClick)=\"onClickOutside($event)\"\n (detach)=\"closeMenu()\"\n (positionChange)=\"onPositionChange($event)\"\n >\n <div\n class=\"ant-select-dropdown ant-cascader-dropdown\"\n [class.ant-select-dropdown-placement-bottomLeft]=\"dropdownPosition === 'bottomLeft'\"\n [class.ant-select-dropdown-placement-bottomRight]=\"dropdownPosition === 'bottomRight'\"\n [class.ant-select-dropdown-placement-topLeft]=\"dropdownPosition === 'topLeft'\"\n [class.ant-select-dropdown-placement-topRight]=\"dropdownPosition === 'topRight'\"\n [class.ant-cascader-dropdown-rtl]=\"dir === 'rtl'\"\n [@slideMotion]=\"'enter'\"\n [@.disabled]=\"!!noAnimation?.nzNoAnimation\"\n [nzNoAnimation]=\"noAnimation?.nzNoAnimation\"\n (mouseenter)=\"onTriggerMouseEnter()\"\n (mouseleave)=\"onTriggerMouseLeave($event)\"\n >\n <div\n #menu\n class=\"ant-cascader-menus\"\n [class.ant-cascader-rtl]=\"dir === 'rtl'\"\n [class.ant-cascader-menus-hidden]=\"!menuVisible\"\n [class.ant-cascader-menu-empty]=\"shouldShowEmpty\"\n [class]=\"nzMenuClassName\"\n [style]=\"nzMenuStyle\"\n >\n @if (shouldShowEmpty) {\n <ul class=\"ant-cascader-menu\" [style.width]=\"dropdownWidthStyle\" [style.height]=\"dropdownHeightStyle\">\n <li class=\"ant-cascader-menu-item ant-cascader-menu-item-disabled\">\n <nz-embed-empty\n class=\"ant-cascader-menu-item-content\"\n [nzComponentName]=\"'cascader'\"\n [specificContent]=\"nzNotFoundContent\"\n />\n </li>\n </ul>\n } @else {\n @for (options of cascaderService.columns; track options; let i = $index) {\n <ul\n class=\"ant-cascader-menu\"\n role=\"menuitemcheckbox\"\n [class]=\"nzColumnClassName\"\n [style.height]=\"dropdownHeightStyle\"\n >\n @for (option of options; track option) {\n <li\n nz-cascader-option\n [expandIcon]=\"nzExpandIcon\"\n [columnIndex]=\"i\"\n [nzLabelProperty]=\"nzLabelProperty\"\n [optionTemplate]=\"nzOptionRender\"\n [activated]=\"isOptionActivated(option, i)\"\n [highlightText]=\"inSearchingMode ? inputValue : ''\"\n [node]=\"option\"\n [dir]=\"dir\"\n [checkable]=\"nzMultiple\"\n (mouseenter)=\"onOptionMouseEnter(option, i, $event)\"\n (mouseleave)=\"onOptionMouseLeave(option, i, $event)\"\n (click)=\"onOptionClick(option, i, $event)\"\n (check)=\"onOptionCheck(option, i)\"\n ></li>\n }\n </ul>\n }\n }\n </div>\n </div>\n </ng-template>\n `,\n animations: [slideMotion],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => NzCascaderComponent),\n multi: true\n },\n { provide: NZ_SPACE_COMPACT_ITEM_TYPE, useValue: 'select' },\n NzCascaderService,\n NzDestroyService,\n NzCascaderTreeService\n ],\n host: {\n '[attr.tabIndex]': '\"0\"',\n '[class.ant-select-in-form-item]': '!!nzFormStatusService',\n '[class.ant-select-lg]': 'finalSize() === \"large\"',\n '[class.ant-select-sm]': 'finalSize() === \"small\"',\n '[class.ant-select-allow-clear]': 'nzAllowClear',\n '[class.ant-select-show-arrow]': 'nzShowArrow',\n '[class.ant-select-show-search]': '!!nzShowSearch',\n '[class.ant-select-disabled]': 'nzDisabled',\n '[class.ant-select-open]': 'menuVisible',\n '[class.ant-select-focused]': 'isFocused',\n '[class.ant-select-multiple]': 'nzMultiple',\n '[class.ant-select-single]': '!nzMultiple',\n '[class.ant-select-rtl]': `dir === 'rtl'`\n },\n hostDirectives: [NzSpaceCompactItemDirective],\n imports: [\n SlicePipe,\n OverlayModule,\n FormsModule,\n NzIconModule,\n NzEmptyModule,\n NzFormItemFeedbackIconComponent,\n NzOverlayModule,\n NzNoAnimationDirective,\n NzSelectClearComponent,\n NzSelectItemComponent,\n NzSelectPlaceholderComponent,\n NzSelectSearchComponent,\n NzCascaderOptionComponent\n ]\n})\nexport class NzCascaderComponent\n extends NzTreeBase\n implements NzCascaderComponentAsSource, OnInit, OnDestroy, OnChanges, ControlValueAccessor\n{\n readonly _nzModuleName: NzConfigKey = NZ_CONFIG_MODULE_NAME;\n\n @ViewChild('selectContainer', { static: false }) selectContainer!: ElementRef;\n\n @ViewChild(NzSelectSearchComponent)\n set input(inputComponent: NzSelectSearchComponent | undefined) {\n this.input$.next(inputComponent?.inputElement);\n }\n\n get input(): ElementRef<HTMLInputElement> | undefined {\n return this.input$.getValue();\n }\n\n /** Used to store the native `<input type=\"search\" />` element since it might be set asynchronously. */\n private input$ = new BehaviorSubject<ElementRef<HTMLInputElement> | undefined>(undefined);\n\n @ViewChild('menu', { static: false }) menu!: ElementRef;\n @ViewChild(CdkConnectedOverlay, { static: false }) overlay!: CdkConnectedOverlay;\n @ViewChildren(NzCascaderOptionComponent) cascaderItems!: QueryList<NzCascaderOptionComponent>;\n\n @Input() nzOptionRender: TemplateRef<{ $implicit: NzCascaderOption; index: number }> | null = null;\n @Input({ transform: booleanAttribute }) nzShowInput = true;\n @Input({ transform: booleanAttribute }) nzShowArrow = true;\n @Input({ transform: booleanAttribute }) nzAllowClear = true;\n @Input({ transform: booleanAttribute }) nzAutoFocus = false;\n @Input({ transform: booleanAttribute }) nzChangeOnSelect = false;\n @Input({ transform: booleanAttribute }) nzDisabled = false;\n @Input() nzColumnClassName?: string;\n @Input() nzExpandTrigger: NzCascaderExpandTrigger = 'click';\n @Input() nzValueProperty: string = 'value';\n @Input() nzLabelProperty: string = 'label';\n @Input() nzLabelRender: TemplateRef<typeof this.labelRenderContext> | null = null;\n @Input() nzNotFoundContent?: string | TemplateRef<void>;\n @Input() @WithConfig() nzSize: NzCascaderSize = 'default';\n @Input() @WithConfig() nzBackdrop = false;\n @Input() nzShowSearch: boolean | NzShowSearchOptions = false;\n @Input() nzPlaceHolder: string = '';\n @Input() nzMenuClassName?: string;\n @Input() nzMenuStyle: NgStyleInterface | null = null;\n /**\n * Duration in milliseconds before opening the menu when the mouse enters the trigger.\n * @default 150\n */\n @Input({ transform: numberAttribute }) nzMouseLeaveDelay: number = 150;\n /**\n * Duration in milliseconds before closing the menu when the mouse leaves the trigger.\n * @default 150\n */\n @Input({ transform: numberAttribute }) nzMouseEnterDelay: number = 150;\n @Input() nzStatus: NzStatus = '';\n @Input({ transform: booleanAttribute }) nzMultiple: boolean = false;\n @Input() nzMaxTagCount: number = Infinity;\n @Input() nzPlacement: NzCascaderPlacement = 'bottomLeft';\n\n @Input() nzTriggerAction: NzCascaderTriggerType | NzCascaderTriggerType[] = ['click'] as NzCascaderTriggerType[];\n @Input() nzChangeOn?: (option: NzCascaderOption, level: number) => boolean;\n @Input() nzLoadData?: (node: NzCascaderOption, index: number) => PromiseLike<NzSafeAny> | Observable<NzSafeAny>;\n @Input() nzDisplayWith: (nodes: NzCascaderOption[]) => string | undefined = (nodes: NzCascaderOption[]) => {\n return defaultDisplayRender(nodes.map(n => this.cascaderService.getOptionLabel(n!)));\n };\n // TODO: RTL\n @Input() nzSuffixIcon: string | TemplateRef<void> = 'down';\n @Input() nzExpandIcon: string | TemplateRef<void> = '';\n\n @Input()\n get nzOptions(): NzCascaderOption[] | null {\n return this.cascaderService.nzOptions;\n }\n\n set nzOptions(options: NzCascaderOption[] | null) {\n const nodes = this.coerceTreeNodes(options || []);\n this.treeService.initTree(nodes);\n this.cascaderService.columns = [nodes];\n this.updateSelectedNodes(true);\n\n if (this.inSearchingMode) {\n this.cascaderService.setSearchingMode(this.inSearchingMode);\n this.cascaderService.prepareSearchOptions(this.inputValue);\n }\n }\n\n get treeService(): NzCascaderTreeService {\n return this.nzTreeService as NzCascaderTreeService;\n }\n\n @Output() readonly nzVisibleChange = new EventEmitter<boolean>();\n @Output() readonly nzSelectionChange = new EventEmitter<NzCascaderOption[]>();\n @Output() readonly nzRemoved = new EventEmitter<NzCascaderOption>();\n @Output() readonly nzClear = new EventEmitter<void>();\n\n prefixCls: string = 'ant-select';\n statusCls: NgClassInterface = {};\n status: NzValidateStatus = '';\n hasFeedback: boolean = false;\n\n /**\n * If the dropdown should show the empty content.\n * `true` if there's no options.\n */\n shouldShowEmpty: boolean = false;\n\n el: HTMLElement;\n menuVisible = false;\n isLoading = false;\n labelRenderText?: string;\n labelRenderContext = {};\n onChange = Function.prototype;\n onTouched = Function.prototype;\n positions: ConnectionPositionPair[] = [...DEFAULT_CASCADER_POSITIONS];\n\n /**\n * Dropdown width in pixel.\n */\n dropdownWidthStyle?: string;\n dropdownHeightStyle: 'auto' | '' = '';\n dropdownPosition: NzCascaderPlacement = 'bottomLeft';\n isFocused = false;\n\n locale!: NzCascaderI18nInterface;\n dir: Direction = 'ltr';\n\n isComposing = false;\n\n protected get overlayOrigin(): ElementRef {\n return this.elementRef;\n }\n\n protected finalSize = computed(() => {\n if (this.compactSize) {\n return this.compactSize();\n }\n return this.size();\n });\n\n private size = signal<NzSizeLDSType>(this.nzSize);\n private compactSize = inject(NZ_SPACE_COMPACT_SIZE, { optional: true });\n private inputString = '';\n private isOpening = false;\n private delayMenuTimer?: ReturnType<typeof setTimeout>;\n private delaySelectTimer?: ReturnType<typeof setTimeout>;\n private isNzDisableFirstChange: boolean = true;\n selectedNodes: NzTreeNode[] = [];\n\n get inSearchingMode(): boolean {\n return this.cascaderService.inSearchingMode;\n }\n\n set inputValue(inputValue: string) {\n this.inputString = inputValue;\n this.toggleSearchingMode(!!inputValue);\n }\n\n get inputValue(): string {\n return this.inputString;\n }\n\n private get hasInput(): boolean {\n return !!this.inputValue;\n }\n\n private get hasValue(): boolean {\n return this.cascaderService.values && this.cascaderService.values.length > 0;\n }\n\n get showLabelRender(): boolean {\n return !this.hasInput && !this.nzMultiple && !!this.selectedNodes.length;\n }\n\n get showPlaceholder(): boolean {\n return !(this.hasInput || this.hasValue);\n }\n\n get clearIconVisible(): boolean {\n return this.nzAllowClear && !this.nzDisabled && (this.hasValue || this.hasInput);\n }\n\n get isLabelRenderTemplate(): boolean {\n return !!this.nzLabelRender;\n }\n\n noAnimation = inject(NzNoAnimationDirective, { host: true, optional: true });\n nzFormStatusService = inject(NzFormStatusService, { optional: true });\n private nzFormNoStatusService = inject(NzFormNoStatusService, { optional: true });\n private nzConfigService = inject(NzConfigService);\n public cascaderService = inject(NzCascaderService);\n\n constructor(\n treeService: NzCascaderTreeService,\n private ngZone: NgZone,\n private cdr: ChangeDetectorRef,\n private i18nService: NzI18nService,\n private destroy$: NzDestroyService,\n private elementRef: ElementRef,\n private renderer: Renderer2,\n private directionality: Directionality\n ) {\n super(treeService);\n this.el = elementRef.nativeElement;\n this.cascaderService.withComponent(this);\n this.renderer.addClass(this.elementRef.nativeElement, 'ant-select');\n this.renderer.addClass(this.elementRef.nativeElement, 'ant-cascader');\n }\n\n ngOnInit(): void {\n this.nzFormStatusService?.formStatusChanges\n .pipe(\n distinctUntilChanged((pre, cur) => pre.status === cur.status && pre.hasFeedback === cur.hasFeedback),\n withLatestFrom(this.nzFormNoStatusService ? this.nzFormNoStatusService.noFormStatus : of(false)),\n map(([{ status, hasFeedback }, noStatus]) => ({ status: noStatus ? '' : status, hasFeedback })),\n takeUntil(this.destroy$)\n )\n .subscribe(({ status, hasFeedback }) => {\n this.setStatusStyles(status, hasFeedback);\n });\n\n const srv = this.cascaderService;\n\n srv.$redraw.pipe(takeUntil(this.destroy$)).subscribe(() => {\n // These operations would not mutate data.\n this.checkChildren();\n this.setDisplayLabel();\n this.cdr.detectChanges();\n this.reposition();\n this.setDropdownStyles();\n });\n\n srv.$loading.pipe(takeUntil(this.destroy$)).subscribe(loading => {\n this.isLoading = loading;\n });\n\n srv.$nodeSelected.pipe(takeUntil(this.destroy$)).subscribe(node => {\n if (!node) {\n this.emitValue([]);\n this.nzSelectionChange.emit([]);\n } else {\n const shouldClose =\n // keep menu opened if multiple mode\n !this.nzMultiple && (node.isLeaf || (this.nzChangeOnSelect && this.nzExpandTrigger === 'hover'));\n if (shouldClose) {\n this.delaySetMenuVisible(false);\n }\n this.nzSelectionChange.emit(this.getAncestorOptionList(node));\n this.cdr.markForCheck();\n }\n });\n\n srv.$quitSearching.pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.inputValue = '';\n this.dropdownWidthStyle = '';\n });\n\n this.i18nService.localeChange.pipe(startWith(), takeUntil(this.destroy$)).subscribe(() => {\n this.setLocale();\n });\n\n this.size.set(this.nzSize);\n this.nzConfigService\n .getConfigChangeEventForComponent(NZ_CONFIG_MODULE_NAME)\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => {\n this.size.set(this.nzSize);\n this.cdr.markForCheck();\n });\n\n this.dir = this.directionality.value;\n this.directionality.change.pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.dir = this.directionality.value;\n srv.$redraw.next();\n });\n\n this.setupSelectionChangeListener();\n this.setupChangeListener();\n this.setupKeydownListener();\n this.setupFocusListener();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n const { nzStatus, nzSize, nzPlacement } = changes;\n if (nzStatus) {\n this.setStatusStyles(this.nzStatus, this.hasFeedback);\n }\n if (nzSize) {\n this.size.set(nzSize.currentValue);\n }\n if (nzPlacement) {\n const { currentValue } = nzPlacement;\n this.dropdownPosition = currentValue as NzCascaderPlacement;\n const listOfPlacement = ['bottomLeft', 'topLeft', 'bottomRight', 'topRight'];\n if (currentValue && listOfPlacement.includes(currentValue)) {\n this.positions = [POSITION_MAP[currentValue as POSITION_TYPE]];\n } else {\n this.positions = listOfPlacement.map(e => POSITION_MAP[e as POSITION_TYPE]);\n }\n }\n }\n\n ngOnDestroy(): void {\n this.clearDelayMenuTimer();\n this.clearDelaySelectTimer();\n }\n\n registerOnChange(fn: () => {}): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => {}): void {\n this.onTouched = fn;\n }\n\n writeValue(value: NzSafeAny): void {\n if (isNotNil(value)) {\n if (this.nzMultiple) {\n this.cascaderService.values = toArray(value);\n } else {\n this.cascaderService.values = [toArray(value)];\n }\n // need clear selected nodes when user set value before updating\n this.clearSelectedNodes();\n this.updateSelectedNodes(true, false);\n } else {\n this.cascaderService.values = [];\n this.clearSelectedNodes();\n this.selectedNodes = [];\n this.cascaderService.$redraw.next();\n }\n }\n\n private setupSelectionChangeListener(): void {\n merge(this.nzSelectionChange, this.nzRemoved, this.nzClear)\n .pipe(takeUntil(this.destroy$))\n .subscribe(() => {\n this.updateSelectedNodes();\n this.emitValue(this.cascaderService.values);\n this.cascaderService.$redraw.next();\n });\n }\n\n delaySetMenuVisible(visible: boolean, delay: number = 100, setOpening: boolean = false): void {\n this.clearDelayMenuTimer();\n if (delay) {\n if (visible && setOpening) {\n this.isOpening = true;\n }\n this.delayMenuTimer = setTimeout(() => {\n this.setMenuVisible(visible);\n this.cdr.detectChanges();\n this.clearDelayMenuTimer();\n if (visible) {\n setTimeout(() => {\n this.isOpening = false;\n }, 100);\n }\n }, delay);\n } else {\n this.setMenuVisible(visible);\n }\n }\n\n setMenuVisible(visible: boolean): void {\n if (this.nzDisabled || this.menuVisible === visible) {\n return;\n }\n if (visible) {\n this.cascaderService.$redraw.next();\n this.updateSelectedNodes(!!this.nzLoadData);\n this.scrollToActivatedOptions();\n } else {\n this.inputValue = '';\n }\n\n this.menuVisible = visible;\n this.nzVisibleChange.emit(visible);\n this.cdr.detectChanges();\n }\n\n private clearDelayMenuTimer(): void {\n if (this.delayMenuTimer) {\n clearTimeout(this.delayMenuTimer);\n this.delayMenuTimer = undefined;\n }\n }\n\n clearSelection(event?: Event): void {\n if (event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n this.clearSelectedNodes();\n this.labelRenderText = '';\n this.labelRenderContext = {};\n this.inputValue = '';\n this.setMenuVisible(false);\n this.cascaderService.clear();\n this.nzClear.emit();\n }\n\n clearSelectedNodes(): void {\n this.selectedNodes.forEach(node => {\n this.removeSelected(node, false);\n });\n }\n\n emitValue(values: NzSafeAny[] | null): void {\n if (this.nzMultiple) {\n this.onChange(values);\n } else {\n this.onChange(values?.length ? values[0] : []);\n }\n }\n\n /**\n * @internal\n */\n getSubmitValue(): NzSafeAny[] {\n if (this.nzMultiple) {\n return this.cascaderService.values;\n } else {\n return this.cascaderService.values?.length ? this.cascad