-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(module:cascader): support multiple selection (#8903)
* feat(module:cascader): support multiple selection * feat(module:cascader): support multiple selection * feat(module:cascader): support multiple selection * feat(module:cascader): support multiple selection - disabled * feat(module:cascader): support multiple selection - showSearch * feat(module:cascader): support multiple selection - showSearch * feat(module:cascader): support multiple selection - default value * feat(module:cascader): support multiple selection - load data * feat(module:cascader): support multiple selection - lazy load * feat(module:cascader): support multiple selection - search mode * feat(module:cascader): update docs and remove useless code * refactor(module:cascader): remove cdkOverlayOrigin wrapper * refactor(module:cascader): remove nzSelect * feat(module:cascader): click leaf node set check state in multiple mode * fix(module:cascader): hide selected item when searching in single mode * fix(module:cascader): quit searching when check in multiple mode
- Loading branch information
Showing
25 changed files
with
1,273 additions
and
574 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/** | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE | ||
*/ | ||
|
||
import { Injectable } from '@angular/core'; | ||
|
||
import { NzTreeBaseService, NzTreeNode, NzTreeNodeKey } from 'ng-zorro-antd/core/tree'; | ||
import { NzSafeAny } from 'ng-zorro-antd/core/types'; | ||
import { arraysEqual, isNotNil } from 'ng-zorro-antd/core/util'; | ||
|
||
import { NzCascaderOption } from './typings'; | ||
|
||
interface InternalFieldNames { | ||
label: string; | ||
value: string; | ||
} | ||
|
||
@Injectable() | ||
export class NzCascaderTreeService extends NzTreeBaseService { | ||
fieldNames: InternalFieldNames = { | ||
label: 'label', | ||
value: 'value' | ||
}; | ||
missingNodeList: NzTreeNode[] = []; | ||
|
||
override treeNodePostProcessor = (node: NzTreeNode): void => { | ||
node.key = this.getOptionValue(node); | ||
node.title = this.getOptionLabel(node); | ||
}; | ||
|
||
getOptionValue(node: NzTreeNode): NzSafeAny { | ||
return node.origin[this.fieldNames.value || 'value']; | ||
} | ||
|
||
getOptionLabel(node: NzTreeNode): string { | ||
return node.origin[this.fieldNames.label || 'label']; | ||
} | ||
|
||
get children(): NzTreeNode[] { | ||
return this.rootNodes; | ||
} | ||
|
||
set children(value: Array<NzTreeNode | NzSafeAny>) { | ||
this.rootNodes = value.map(v => (v instanceof NzTreeNode ? v : new NzTreeNode(v, null))); | ||
} | ||
|
||
constructor() { | ||
super(); | ||
} | ||
|
||
/** | ||
* Map list of nodes to list of option | ||
*/ | ||
toOptions(nodes: NzTreeNode[]): NzCascaderOption[] { | ||
return nodes.map(node => node.origin); | ||
} | ||
|
||
getAncestorNodeList(node: NzTreeNode | null): NzTreeNode[] { | ||
if (!node) { | ||
return []; | ||
} | ||
if (node.parentNode) { | ||
return [...this.getAncestorNodeList(node.parentNode), node]; | ||
} | ||
return [node]; | ||
} | ||
|
||
/** | ||
* Render by nzCheckedKeys | ||
* When keys equals null, just render with checkStrictly | ||
* | ||
* @param paths | ||
* @param checkStrictly | ||
*/ | ||
conductCheckPaths(paths: NzTreeNodeKey[][] | null, checkStrictly: boolean): void { | ||
this.checkedNodeList = []; | ||
this.halfCheckedNodeList = []; | ||
this.missingNodeList = []; | ||
const existsPathList: NzTreeNodeKey[][] = []; | ||
const calc = (nodes: NzTreeNode[]): void => { | ||
nodes.forEach(node => { | ||
if (paths === null) { | ||
// render tree if no default checked keys found | ||
node.isChecked = !!node.origin.checked; | ||
} else { | ||
// if node is in checked path | ||
const nodePath = this.getAncestorNodeList(node).map(n => this.getOptionValue(n)); | ||
if (paths.some(keys => arraysEqual(nodePath, keys))) { | ||
node.isChecked = true; | ||
node.isHalfChecked = false; | ||
existsPathList.push(nodePath); | ||
} else { | ||
node.isChecked = false; | ||
node.isHalfChecked = false; | ||
} | ||
} | ||
if (node.children.length > 0) { | ||
calc(node.children); | ||
} | ||
}); | ||
}; | ||
calc(this.rootNodes); | ||
this.refreshCheckState(checkStrictly); | ||
this.missingNodeList = this.getMissingNodeList(paths, existsPathList); | ||
} | ||
|
||
conductSelectedPaths(paths: NzTreeNodeKey[][], isMulti: boolean): void { | ||
this.selectedNodeList.forEach(node => (node.isSelected = false)); | ||
this.selectedNodeList = []; | ||
this.missingNodeList = []; | ||
const existsPathList: NzTreeNodeKey[][] = []; | ||
const calc = (nodes: NzTreeNode[]): boolean => | ||
nodes.every(node => { | ||
// if node is in selected path | ||
const nodePath = this.getAncestorNodeList(node).map(n => this.getOptionValue(n)); | ||
if (paths.some(keys => arraysEqual(nodePath, keys))) { | ||
node.isSelected = true; | ||
this.setSelectedNodeList(node); | ||
existsPathList.push(nodePath); | ||
if (!isMulti) { | ||
// if not support multi select | ||
return false; | ||
} | ||
} else { | ||
node.isSelected = false; | ||
} | ||
if (node.children.length > 0) { | ||
// Recursion | ||
return calc(node.children); | ||
} | ||
return true; | ||
}); | ||
calc(this.rootNodes); | ||
this.missingNodeList = this.getMissingNodeList(paths, existsPathList); | ||
} | ||
|
||
private getMissingNodeList(paths: NzTreeNodeKey[][] | null, existsPathList: NzTreeNodeKey[][]): NzTreeNode[] { | ||
if (!paths) { | ||
return []; | ||
} | ||
return paths | ||
.filter(path => !existsPathList.some(keys => arraysEqual(path, keys))) | ||
.map(path => this.createMissingNode(path)) | ||
.filter(isNotNil); | ||
} | ||
|
||
private createMissingNode(path: NzTreeNodeKey[]): NzTreeNode | null { | ||
if (!path?.length) { | ||
return null; | ||
} | ||
|
||
const createOption = (key: NzTreeNodeKey): NzSafeAny => { | ||
return { | ||
[this.fieldNames.value || 'value']: key, | ||
[this.fieldNames.label || 'label']: key | ||
}; | ||
}; | ||
|
||
let node = new NzTreeNode(createOption(path[0]), null, this); | ||
|
||
for (let i = 1; i < path.length; i++) { | ||
const childNode = new NzTreeNode(createOption(path[i])); | ||
node.addChildren([childNode]); | ||
node = childNode; | ||
} | ||
|
||
return node; | ||
} | ||
} |
Oops, something went wrong.