* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import type {
ICascaderPanelConfig,
ICascaderPanelData,
ICascaderPanelNodePropValue,
ICascaderPanelNodeValue
} from '@/types'
import { isEqual } from '@opentiny/utils'
import { capitalize } from '@opentiny/utils'
import { isNull } from '@opentiny/utils'
let uid = 0
export default class Node {
parent: Node | null
level: number
data!: ICascaderPanelData
config!: ICascaderPanelConfig
uid!: number
value!: ICascaderPanelNodeValue
label!: string
pathNodes!: Node[]
path!: ICascaderPanelNodeValue[]
pathLabels!: string[]
loaded!: boolean
loading!: boolean
hasChildren!: boolean
children!: Node[]
checked?: boolean
indeterminate?: boolean
root?: boolean
constructor(data: ICascaderPanelData, config: ICascaderPanelConfig, parentNode?: Node | null) {
Object.assign(this, { data, config, uid: uid++ })
this.parent = parentNode || null
this.level = !this.parent ? 1 : this.parent.level + 1
this.initState()
this.initChildren()
}
initState() {
const { value, label } = this.config
this.value = this.data[value] as ICascaderPanelNodeValue
this.label = String(this.data[label])
this.pathNodes = this.calculatePathNodes()
this.path = this.pathNodes.map((node) => node.value)
this.pathLabels = this.pathNodes.map((node) => node.label)
Object.assign(this, { loading: false, loaded: false })
}
initChildren() {
const { config } = this
const childrenKey = config.children
const childrenData = this.data[childrenKey]
this.hasChildren = Array.isArray(childrenData)
this.children = ((childrenData as ICascaderPanelData[]) || []).map((child) => new Node(child, config, this))
}
get isLeaf(): boolean {
const { data, loaded, hasChildren, children, config } = this
const { lazy, leaf } = config
if (lazy) {
const leafValue = data[leaf]
const isLeaf = !isNull(leafValue) ? leafValue : loaded ? !children.length : false
this.hasChildren = !isLeaf
return Boolean(isLeaf)
}
return !hasChildren
}
* 当前节点是否被disable
*/
get isDisabled(): boolean {
const { data, parent, config } = this
const { checkStrictly, disabled } = config
const disabledKey = disabled
return Boolean(data[disabledKey] || (!checkStrictly && parent && parent.isDisabled))
}
calculatePathNodes() {
const nodes: Node[] = [this]
let parentNode = this.parent
while (parentNode) {
nodes.unshift(parentNode)
parentNode = parentNode.parent
}
return nodes
}
getValue() {
return this.value
}
getPath() {
return this.path
}
getValueByOption(): ICascaderPanelNodePropValue {
return this.config.emitPath ? this.getPath() : this.getValue()
}
*
* @param allLevels 输入框中是否显示选中值的完整路径
* @param separator 分割符
* @returns 选中值的路径
*/
getText(allLevels: boolean, separator: string) {
return allLevels ? this.pathLabels.join(separator) : this.label
}
isSameNode(checkedValue: ICascaderPanelNodePropValue) {
const value = this.getValueByOption()
const isMultiple = this.config.multiple && Array.isArray(checkedValue)
return isMultiple
? checkedValue.some((val) => isEqual(val, value as object))
: isEqual(checkedValue as object, value as object)
}
emit(eventName: string, ...args) {
const { parent } = this
const handlerName = `onChild${capitalize(eventName)}`
if (parent) {
parent[handlerName] && parent[handlerName](...args)
parent.emit(eventName, ...args)
}
}
broadcast(eventName: string, ...args) {
const handlerName = `onParent${capitalize(eventName)}`
this.children.forEach((child) => {
if (child) {
child.broadcast(eventName, ...args)
child[handlerName] && child[handlerName](...args)
}
})
}
onParentCheck(checked: boolean) {
!this.isDisabled && this.setCheckState(checked)
}
onChildCheck() {
const { children } = this
const validChildren = children.filter((child) => !child.isDisabled)
const checked = validChildren.length ? validChildren.every((child) => child.checked) : false
this.setCheckState(checked)
}
syncCheckState(checkedValue: ICascaderPanelNodePropValue) {
const checked = this.isSameNode(checkedValue)
this.doCheck(checked)
}
setCheckState(checked: boolean) {
const totalNum = this.children.length
const checkedNum = this.children.reduce((prevChild, nextChild) => {
const num = nextChild.checked ? 1 : nextChild.indeterminate ? 0.5 : 0
return prevChild + num
}, 0)
this.checked = checked
this.indeterminate = checkedNum !== totalNum && checkedNum > 0
}
doCheck(isChecked: boolean) {
if (this.checked !== isChecked) {
if (this.config.checkStrictly) {
this.checked = isChecked
} else {
this.broadcast('check', isChecked)
this.setCheckState(isChecked)
this.emit('check')
}
}
}
}