ant-design-vue/components/vc-tree-select/src/util.js

570 lines
15 KiB
JavaScript

import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, filterEmpty, getSlots } from '../../_util/props-util'
import { cloneVNodes, cloneElement } from '../../_util/vnode'
export function toTitle (title) {
if (typeof title === 'string') {
return title
}
return null
}
export function getValuePropValue (child) {
const props = getAllProps(child)
if ('value' in props) {
return props.value
}
if (getKey(child) !== undefined) {
return getKey(child)
}
throw new Error(`no key or value for ${child}`)
}
export function getPropValue (child, prop) {
if (prop === 'value') {
return getValuePropValue(child)
}
const slots = getSlots(child)
if (prop === 'children') {
const newChild = child.$slots ? cloneVNodes(child.$slots.default, true) : cloneVNodes(child.componentOptions.children, true)
if (newChild.length === 1 && !newChild[0].tag) {
return newChild[0].text
}
return newChild
}
if (slots[prop]) {
return cloneVNodes(slots[prop], true)
}
const data = getPropsData(child)
if (prop in data) {
return data[prop]
} else {
return getAttrs(child)[prop]
}
}
export function isMultiple (props) {
return !!(props.multiple || props.treeCheckable)
}
export function toArray (value) {
let ret = value
if (value === undefined) {
ret = []
} else if (!Array.isArray(value)) {
ret = [value]
}
return ret
}
export function preventDefaultEvent (e) {
e.preventDefault()
}
export const UNSELECTABLE_STYLE = {
userSelect: 'none',
WebkitUserSelect: 'none',
}
export const UNSELECTABLE_ATTRIBUTE = {
unselectable: 'unselectable',
}
export function labelCompatible (prop) {
let newProp = prop
if (newProp === 'label') {
newProp = 'title'
}
return newProp
}
export function isInclude (smallArray, bigArray) {
// attention: [0,0,1] [0,0,10]
return smallArray.every((ii, i) => {
return ii === bigArray[i]
})
}
export function isPositionPrefix (smallPos, bigPos) {
if (!bigPos || !smallPos) {
// console.log(smallPos, bigPos);
return false
}
if (bigPos.length < smallPos.length) {
return false
}
// attention: "0-0-1" "0-0-10"
if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
return false
}
return bigPos.substr(0, smallPos.length) === smallPos
}
/*
export function getCheckedKeys(node, checkedKeys, allCheckedNodesKeys) {
const nodeKey = node.props.eventKey;
let newCks = [...checkedKeys];
let nodePos;
const unCheck = allCheckedNodesKeys.some(item => {
if (item.key === nodeKey) {
nodePos = item.pos;
return true;
}
});
if (unCheck) {
newCks = [];
allCheckedNodesKeys.forEach(item => {
if (isPositionPrefix(item.pos, nodePos) || isPositionPrefix(nodePos, item.pos)) {
return;
}
newCks.push(item.key);
});
} else {
newCks.push(nodeKey);
}
return newCks;
}
*/
function getChildrenlength (children) {
let len = 1
if (Array.isArray(children)) {
len = children.length
}
return len
}
function getSiblingPosition (index, len, siblingPosition) {
if (len === 1) {
siblingPosition.first = true
siblingPosition.last = true
} else {
siblingPosition.first = index === 0
siblingPosition.last = index === len - 1
}
return siblingPosition
}
function filterChild (childs) {
const newChilds = []
childs.forEach(child => {
const options = getSlotOptions(child)
if (options.__ANT_TREE_NODE || options.__ANT_TREE_SELECT_NODE) {
newChilds.push(child)
}
})
return newChilds
}
export function loopAllChildren (childs, callback, parent) {
const loop = (children, level, _parent) => {
const len = getChildrenlength(children)
children.forEach(function handler(item, index) { // eslint-disable-line
const pos = `${level}-${index}`
if (item && item.componentOptions && item.componentOptions.children) {
loop(filterChild(item.componentOptions.children), pos, { node: item, pos })
}
if (item) {
callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent)
}
})
}
loop(filterChild(childs), 0, parent)
}
// export function loopAllChildren(childs, callback) {
// const loop = (children, level) => {
// React.Children.forEach(children, (item, index) => {
// const pos = `${level}-${index}`;
// if (item && item.props.children) {
// loop(item.props.children, pos);
// }
// if (item) {
// callback(item, index, pos, getValuePropValue(item));
// }
// });
// };
// loop(childs, 0);
// }
// TODO: Here has the side effect. Update node children data affect.
export function flatToHierarchy (arr) {
if (!arr.length) {
return arr
}
const hierarchyNodes = []
const levelObj = {}
arr.forEach((item) => {
if (!item.pos) {
return
}
const posLen = item.pos.split('-').length
if (!levelObj[posLen]) {
levelObj[posLen] = []
}
levelObj[posLen].push(item)
})
const levelArr = Object.keys(levelObj).sort((a, b) => b - a)
// const s = Date.now();
// todo: there are performance issues!
levelArr.reduce((pre, cur) => {
if (cur && cur !== pre) {
levelObj[pre].forEach((item) => {
let haveParent = false
levelObj[cur].forEach((ii) => {
if (isPositionPrefix(ii.pos, item.pos)) {
haveParent = true
if (!ii.children) {
ii.children = []
}
ii.children.push(item)
}
})
if (!haveParent) {
hierarchyNodes.push(item)
}
})
}
return cur
})
// console.log(Date.now() - s);
return levelObj[levelArr[levelArr.length - 1]].concat(hierarchyNodes)
}
// arr.length === 628, use time: ~20ms
export function filterParentPosition (arr) {
const levelObj = {}
arr.forEach((item) => {
const posLen = item.split('-').length
if (!levelObj[posLen]) {
levelObj[posLen] = []
}
levelObj[posLen].push(item)
})
const levelArr = Object.keys(levelObj).sort()
for (let i = 0; i < levelArr.length; i++) {
if (levelArr[i + 1]) {
levelObj[levelArr[i]].forEach(ii => {
for (let j = i + 1; j < levelArr.length; j++) {
levelObj[levelArr[j]].forEach((_i, index) => {
if (isPositionPrefix(ii, _i)) {
levelObj[levelArr[j]][index] = null
}
})
levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p)
}
})
}
}
let nArr = []
levelArr.forEach(i => {
nArr = nArr.concat(levelObj[i])
})
return nArr
}
// console.log(filterParentPosition(
// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1']
// ));
function stripTail (str) {
const arr = str.match(/(.+)(-[^-]+)$/)
let st = ''
if (arr && arr.length === 3) {
st = arr[1]
}
return st
}
function splitPosition (pos) {
return pos.split('-')
}
// todo: do optimization.
export function handleCheckState (obj, checkedPositionArr, checkIt) {
// console.log(stripTail('0-101-000'));
// let s = Date.now();
let objKeys = Object.keys(obj)
objKeys.forEach((i, index) => {
const iArr = splitPosition(i)
let saved = false
checkedPositionArr.forEach((_pos) => {
const _posArr = splitPosition(_pos)
if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) {
obj[i].halfChecked = false
obj[i].checked = checkIt
objKeys[index] = null
}
if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) {
saved = true
}
})
if (!saved) {
objKeys[index] = null
}
})
objKeys = objKeys.filter(i => i) // filter non null;
for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) {
// loop to set ancestral nodes's `checked` or `halfChecked`
const loop = (__pos) => {
const _posLen = splitPosition(__pos).length
if (_posLen <= 2) { // e.g. '0-0', '0-1'
return
}
let sibling = 0
let siblingChecked = 0
const parentPosition = stripTail(__pos)
objKeys.forEach((i /* , index*/) => {
const iArr = splitPosition(i)
if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) {
sibling++
if (obj[i].checked) {
siblingChecked++
const _i = checkedPositionArr.indexOf(i)
if (_i > -1) {
checkedPositionArr.splice(_i, 1)
if (_i <= pIndex) {
pIndex--
}
}
} else if (obj[i].halfChecked) {
siblingChecked += 0.5
}
// objKeys[index] = null;
}
})
// objKeys = objKeys.filter(i => i); // filter non null;
const parent = obj[parentPosition]
// not check, checked, halfChecked
if (siblingChecked === 0) {
parent.checked = false
parent.halfChecked = false
} else if (siblingChecked === sibling) {
parent.checked = true
parent.halfChecked = false
} else {
parent.halfChecked = true
parent.checked = false
}
loop(parentPosition)
}
loop(checkedPositionArr[pIndex], pIndex)
}
// console.log(Date.now()-s, objKeys.length, checkIt);
}
function getCheck (treeNodesStates, checkedPositions) {
const halfCheckedKeys = []
const checkedKeys = []
const checkedNodes = []
Object.keys(treeNodesStates).forEach((item) => {
const itemObj = treeNodesStates[item]
if (itemObj.checked) {
checkedKeys.push(itemObj.key)
// checkedNodes.push(getValuePropValue(itemObj.node));
checkedNodes.push({ ...itemObj, pos: item })
} else if (itemObj.halfChecked) {
halfCheckedKeys.push(itemObj.key)
}
})
return {
halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
}
}
export function getTreeNodesStates (children, values) {
const checkedPositions = []
const treeNodesStates = {}
loopAllChildren(children, (item, index, pos, keyOrPos, siblingPosition) => {
treeNodesStates[pos] = {
node: item,
key: keyOrPos,
checked: false,
halfChecked: false,
siblingPosition,
}
if (values.indexOf(getValuePropValue(item)) !== -1) {
treeNodesStates[pos].checked = true
checkedPositions.push(pos)
}
})
handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)
return getCheck(treeNodesStates, checkedPositions)
}
// can add extra prop to every node.
export function recursiveCloneChildren (children, cb = ch => ch) {
// return React.Children.map(children, child => {
return Array.from(children).map(child => {
const newChild = cb(child)
if (newChild && newChild.props && newChild.props.children) {
return cloneElement(newChild, {
children: recursiveCloneChildren(newChild.props.children, cb),
})
}
return newChild
})
}
// const newChildren = recursiveCloneChildren(children, child => {
// const extraProps = {};
// if (child && child.type && child.type.xxx) {
// extraProps._prop = true;
// return React.cloneElement(child, extraProps);
// }
// return child;
// });
function recursiveGen (children, level = 0) {
return children.map((child, index) => {
const pos = `${level}-${index}`
const props = getAllProps(child)
const { title, label, value, ...rest } = props
const { children: subChildren } = child.componentOptions
const o = {
...rest,
title,
label: label || title,
value,
key: child.key,
_pos: pos,
}
if (subChildren) {
o.children = recursiveGen(subChildren, pos)
}
return o
})
}
function recursive (children, cb) {
children.forEach(item => {
cb(item)
if (item.children) {
recursive(item.children, cb)
}
})
}
// Get the tree's checkedNodes (todo: can merge to the `handleCheckState` function)
// If one node checked, it's all children nodes checked.
// If sibling nodes all checked, the parent checked.
export function filterAllCheckedData (vs, treeNodes) {
const vals = [...vs]
if (!vals.length) {
return vals
}
const data = recursiveGen(treeNodes)
const checkedNodesPositions = []
function checkChildren (children) {
children.forEach(item => {
if (item.__checked) {
return
}
const ci = vals.indexOf(item.value)
const childs = item.children
if (ci > -1) {
item.__checked = true
checkedNodesPositions.push({ node: item, pos: item._pos })
vals.splice(ci, 1)
if (childs) {
recursive(childs, child => {
child.__checked = true
checkedNodesPositions.push({ node: child, pos: child._pos })
})
}
} else {
if (childs) {
checkChildren(childs)
}
}
})
}
function checkParent (children, parent = { root: true }) {
let siblingChecked = 0
children.forEach(item => {
const childs = item.children
if (childs && !item.__checked && !item.__halfChecked) {
const p = checkParent(childs, item)
if (p.__checked) {
siblingChecked++
} else if (p.__halfChecked) {
siblingChecked += 0.5
}
} else if (item.__checked) {
siblingChecked++
} else if (item.__halfChecked) {
siblingChecked += 0.5
}
})
const len = children.length
if (siblingChecked === len) {
parent.__checked = true
checkedNodesPositions.push({ node: parent, pos: parent._pos })
} else if (siblingChecked < len && siblingChecked > 0) {
parent.__halfChecked = true
}
if (parent.root) {
return children
}
return parent
}
checkChildren(data)
checkParent(data)
checkedNodesPositions.forEach((i, index) => {
// clear private metadata
delete checkedNodesPositions[index].node.__checked
delete checkedNodesPositions[index].node._pos
// create the same structure of `onCheck`'s return.
checkedNodesPositions[index].node.props = {
title: checkedNodesPositions[index].node.title,
label: checkedNodesPositions[index].node.label || checkedNodesPositions[index].node.title,
value: checkedNodesPositions[index].node.value,
}
if (checkedNodesPositions[index].node.children) {
checkedNodesPositions[index].node.props.children = checkedNodesPositions[index].node.children
}
delete checkedNodesPositions[index].node.title
delete checkedNodesPositions[index].node.label
delete checkedNodesPositions[index].node.value
delete checkedNodesPositions[index].node.children
})
return checkedNodesPositions
}
export function processSimpleTreeData (treeData, format) {
function unflatten2 (array, parent = { [format.id]: format.rootPId }) {
const children = []
for (let i = 0; i < array.length; i++) {
array[i] = { ...array[i] } // copy, can not corrupts original data
if (array[i][format.pId] === parent[format.id]) {
array[i].key = array[i][format.id]
children.push(array[i])
array.splice(i--, 1)
}
}
if (children.length) {
parent.children = children
children.forEach(child => unflatten2(array, child))
}
if (parent[format.id] === format.rootPId) {
return children
}
}
return unflatten2(treeData)
}
export function saveRef (instance, name) {
if (!instance.saveRefs) {
instance.saveRefs = {}
}
if (!instance.saveRefs[name]) {
instance.saveRefs[name] = (node) => {
instance[name] = node
}
}
return instance.saveRefs[name]
}