diff --git a/src/tree-select/tree-select.jsx b/src/tree-select/tree-select.jsx
index 92e379d8c1..7752cbdd78 100644
--- a/src/tree-select/tree-select.jsx
+++ b/src/tree-select/tree-select.jsx
@@ -319,10 +319,10 @@ export default class TreeSelect extends Component {
switch (treeCheckedStrategy) {
case 'parent':
- keys = filterChildKey(keys, this._k2n);
+ keys = filterChildKey(keys, this._k2n, this._p2n);
break;
case 'child':
- keys = filterParentKey(keys, this._k2n);
+ keys = filterParentKey(keys, this._k2n, this._p2n);
break;
default:
break;
diff --git a/src/tree/view/tree.jsx b/src/tree/view/tree.jsx
index 0fa29001e5..551d53bada 100644
--- a/src/tree/view/tree.jsx
+++ b/src/tree/view/tree.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable max-depth */
import React, { Component, Children, cloneElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
@@ -10,6 +11,8 @@ import {
filterChildKey,
filterParentKey,
getAllCheckedKeys,
+ forEachEnableNode,
+ isNodeChecked,
} from './util';
const { bindCtx, noop } = func;
@@ -703,19 +706,32 @@ export default class Tree extends Component {
const pos = this._k2n[key].pos;
- const ps = Object.keys(this._p2n);
- ps.forEach(p => {
- if (isDescendantOrSelf(pos, p)) {
- this.processKey(checkedKeys, this._p2n[p].key, check);
- }
+ forEachEnableNode(this._k2n[key], node => {
+ if (node.checkable === false) return;
+ this.processKey(checkedKeys, node.key, check);
});
+ const ps = Object.keys(this._p2n);
+ // ps.forEach(p => {
+ // if (this._p2n[p].checkable !== false && !this._p2n[p].disabled && isDescendantOrSelf(pos, p)) {
+ // this.processKey(checkedKeys, this._p2n[p].key, check);
+ // }
+ // });
+
let currentPos = pos;
const nums = pos.split('-');
for (let i = nums.length; i > 2; i--) {
let parentCheck = true;
const parentPos = nums.slice(0, i - 1).join('-');
+ if (
+ this._p2n[parentPos].disabled ||
+ this._p2n[parentPos].checkboxDisabled ||
+ this._p2n[parentPos].checkable === false
+ ) {
+ currentPos = parentPos;
+ continue;
+ }
const parentKey = this._p2n[parentPos].key;
const parentChecked = checkedKeys.indexOf(parentKey) > -1;
if (!check && !parentChecked) {
@@ -724,12 +740,34 @@ export default class Tree extends Component {
for (let j = 0; j < ps.length; j++) {
const p = ps[j];
- if (isSiblingOrSelf(currentPos, p)) {
- const k = this._p2n[p].key;
- if (checkedKeys.indexOf(k) === -1) {
+ const pnode = this._p2n[p];
+ if (
+ isSiblingOrSelf(currentPos, p) &&
+ !pnode.disabled &&
+ !pnode.checkboxDisabled
+ ) {
+ const k = pnode.key;
+ if (pnode.checkable === false) {
+ // eslint-disable-next-line max-depth
+ if (!pnode.children || pnode.children.length === 0)
+ continue;
+
+ // eslint-disable-next-line max-depth
+ for (let m = 0; m < pnode.children.length; m++) {
+ if (
+ !pnode.children.every(child =>
+ isNodeChecked(child, checkedKeys)
+ )
+ ) {
+ parentCheck = false;
+ break;
+ }
+ }
+ } else if (checkedKeys.indexOf(k) === -1) {
parentCheck = false;
- break;
}
+
+ if (!parentCheck) break;
}
}
@@ -749,10 +787,18 @@ export default class Tree extends Component {
let newCheckedKeys;
switch (checkedStrategy) {
case 'parent':
- newCheckedKeys = filterChildKey(checkedKeys, this._k2n);
+ newCheckedKeys = filterChildKey(
+ checkedKeys,
+ this._k2n,
+ this._p2n
+ );
break;
case 'child':
- newCheckedKeys = filterParentKey(checkedKeys, this._k2n);
+ newCheckedKeys = filterParentKey(
+ checkedKeys,
+ this._k2n,
+ this._p2n
+ );
break;
default:
newCheckedKeys = checkedKeys;
@@ -819,17 +865,24 @@ export default class Tree extends Component {
}
getIndeterminateKeys(checkedKeys) {
+ if (this.props.checkStrictly) {
+ return [];
+ }
+
const indeterminateKeys = [];
const poss = filterChildKey(
checkedKeys.filter(key => !!this._k2n[key]),
- this._k2n
+ this._k2n,
+ this._p2n
).map(key => this._k2n[key].pos);
poss.forEach(pos => {
const nums = pos.split('-');
for (let i = nums.length; i > 2; i--) {
const parentPos = nums.slice(0, i - 1).join('-');
- const parentKey = this._p2n[parentPos].key;
+ const parent = this._p2n[parentPos];
+ if (parent.disabled || parent.checkboxDisabled) break;
+ const parentKey = parent.key;
if (indeterminateKeys.indexOf(parentKey) === -1) {
indeterminateKeys.push(parentKey);
}
diff --git a/src/tree/view/util.js b/src/tree/view/util.js
index 2ba679cfea..89dbbd7f68 100644
--- a/src/tree/view/util.js
+++ b/src/tree/view/util.js
@@ -10,49 +10,109 @@ export function normalizeToArray(keys) {
return [];
}
+
+/**
+ * 判断子节点是否是选中状态,如果 checkable={false} 则向下递归,
+ * @param {Node} child
+ * @param {Array} checkedKeys
+ */
+export function isNodeChecked(node, checkedKeys) {
+ if (node.disabled || parent.checkboxDisabled) return true;
+ /* istanbul ignore next */
+ if (node.checkable === false) {
+ return (
+ !node.children ||
+ node.children.length === 0 ||
+ node.children.every(c => isNodeChecked(c, checkedKeys))
+ );
+ }
+ return checkedKeys.indexOf(node.key) > -1;
+}
+
+/**
+ * 遍历所有可用的子节点
+ * @param {Node}
+ * @param {Function} callback
+ */
+export function forEachEnableNode(node, callback = () => {}) {
+ if (node.disabled || node.checkboxDisabled) return;
+ // eslint-disable-next-line callback-return
+ callback(node);
+ if (node.children && node.children.length > 0) {
+ node.children.forEach(child => forEachEnableNode(child, callback));
+ }
+}
+/**
+ * 判断节点是否禁用checked
+ * @param {Node} node
+ * @returns {Boolean}
+ */
+export function isNodeDisabledChecked(node) {
+ if (node.disabled || node.checkboxDisabled) return true;
+ /* istanbul ignore next */
+ if (node.checkable === false) {
+ return (
+ !node.children ||
+ node.children.length === 0 ||
+ node.children.every(isNodeDisabledChecked)
+ );
+ }
+
+ return false;
+}
+
+/**
+ * 递归获取一个 checkable = {true} 的父节点,当 checkable={false} 时继续往上查找
+ * @param {Node} node
+ * @param {Map} _p2n
+ * @return {Node}
+ */
+export function getCheckableParentNode(node, _p2n) {
+ let parentPos = node.pos.split(['-']);
+ if (parentPos.length === 2) return node;
+ parentPos.splice(parentPos.length - 1, 1);
+ parentPos = parentPos.join('-');
+ const parentNode = _p2n[parentPos];
+ if (parentNode.disabled || parentNode.checkboxDisabled) return false;
+ /* istanbul ignore next */
+ if (parentNode.checkable === false) {
+ return getCheckableParentNode(parentNode, _p2n);
+ }
+
+ return parentNode;
+}
/**
* 过滤子节点
* @param {Array} keys
* @param {Object} _k2n
*/
-export function filterChildKey(keys, _k2n) {
- const newKeys = [...keys]
- .filter(key => !!_k2n[key])
- .sort((prev, next) => {
- return getDepth(prev, _k2n) - getDepth(next, _k2n);
- });
-
- for (let i = 0; i < newKeys.length; i++) {
- for (let j = 0; j < newKeys.length; j++) {
- if (
- i !== j &&
- isDescendantOrSelf(_k2n[newKeys[i]].pos, _k2n[newKeys[j]].pos)
- ) {
- newKeys.splice(j, 1);
- j--;
- }
+export function filterChildKey(keys, _k2n, _p2n) {
+ const newKeys = [];
+ keys.forEach(key => {
+ const node = getCheckableParentNode(_k2n[key], _p2n);
+ if (
+ !node ||
+ node.checkable === false ||
+ node === _k2n[key] ||
+ keys.indexOf(node.key) === -1
+ ) {
+ newKeys.push(key);
}
- }
-
+ });
return newKeys;
}
-export function filterParentKey(keys, _k2n) {
- const newKeys = [...keys]
- .filter(key => !!_k2n[key])
- .sort((prev, next) => {
- return getDepth(next, _k2n) - getDepth(prev, _k2n);
- });
+export function filterParentKey(keys, _k2n, _p2n) {
+ const newKeys = [];
- for (let i = 0; i < newKeys.length; i++) {
- for (let j = 0; j < newKeys.length; j++) {
- if (
- i !== j &&
- isDescendantOrSelf(_k2n[newKeys[j]].pos, _k2n[newKeys[i]].pos)
- ) {
- newKeys.splice(j, 1);
- j--;
- }
+ for (let i = 0; i < keys.length; i++) {
+ const node = _k2n[keys[i]];
+ if (
+ !node.children ||
+ node.children.length === 0 ||
+ node.children.every(isNodeDisabledChecked)
+ ) {
+ newKeys.push(keys[i]);
}
}
@@ -91,10 +151,22 @@ export function isSiblingOrSelf(currentPos, targetPos) {
export function getAllCheckedKeys(checkedKeys, _k2n, _p2n) {
checkedKeys = normalizeToArray(checkedKeys);
const filteredKeys = checkedKeys.filter(key => !!_k2n[key]);
- let flatKeys = filterChildKey(filteredKeys, _k2n);
- const childChecked = child => flatKeys.indexOf(child.key) > -1;
- const removeKey = child => flatKeys.splice(flatKeys.indexOf(child.key), 1);
+ const flatKeys = filterChildKey(filteredKeys, _k2n, _p2n);
+
+ const removeKey = child => {
+ if (child.disabled || child.checkboxDisabled) return;
+ if (
+ child.checkable === false &&
+ child.children &&
+ child.children.length > 0
+ ) {
+ return child.children.forEach(removeKey);
+ }
+ flatKeys.splice(flatKeys.indexOf(child.key), 1);
+ };
+
const addParentKey = (i, parent) => flatKeys.splice(i, 0, parent.key);
+
const keys = [...flatKeys];
for (let i = 0; i < keys.length; i++) {
const pos = _k2n[keys[i]].pos;
@@ -105,7 +177,15 @@ export function getAllCheckedKeys(checkedKeys, _k2n, _p2n) {
for (let j = nums.length - 2; j > 0; j--) {
const parentPos = nums.slice(0, j + 1).join('-');
const parent = _p2n[parentPos];
- const parentChecked = parent.children.every(childChecked);
+ if (
+ parent.checkable === false ||
+ parent.disabled ||
+ parent.checkboxDisabled
+ )
+ continue;
+ const parentChecked = parent.children.every(child =>
+ isNodeChecked(child, flatKeys)
+ );
if (parentChecked) {
parent.children.forEach(removeKey);
addParentKey(i, parent);
@@ -116,20 +196,12 @@ export function getAllCheckedKeys(checkedKeys, _k2n, _p2n) {
}
const newKeys = [];
- if (flatKeys.length) {
- flatKeys = flatKeys.reverse();
- const ps = Object.keys(_p2n);
- for (let i = 0; i < flatKeys.length; i++) {
- const pos = _k2n[flatKeys[i]].pos;
- for (let j = 0; j < ps.length; j++) {
- if (isDescendantOrSelf(pos, ps[j])) {
- newKeys.push(_p2n[ps[j]].key);
- ps.splice(j, 1);
- j--;
- }
- }
- }
- }
+ flatKeys.forEach(key => {
+ forEachEnableNode(_k2n[key], node => {
+ if (node.checkable === false) return;
+ newKeys.push(node.key);
+ });
+ });
return newKeys;
}
diff --git a/test/tree-select/index-spec.js b/test/tree-select/index-spec.js
index 1f60cdc2f6..5b93db90f5 100644
--- a/test/tree-select/index-spec.js
+++ b/test/tree-select/index-spec.js
@@ -310,19 +310,20 @@ describe('TreeSelect', () => {
let triggered = false;
const initValue = '4';
const appendValue = '6';
- const expectValue = ['3', '4'];
+ const expectValue = ['4', '3'];
const handleChange = (value, data) => {
triggered = true;
assert.deepEqual(value, expectValue);
assert.deepEqual(data, expectValue.map(v => _v2n[v]));
};
-
wrapper = mount(
@@ -361,7 +362,9 @@ describe('TreeSelect', () => {
wrapper = mount(
@@ -387,12 +390,14 @@ describe('TreeSelect', () => {
defaultVisible
treeDefaultExpandAll
treeCheckable
- dataSource={dataSource}
+ dataSource={cloneData(dataSource, {
+ 2: { disabled: false }
+ })}
defaultValue={['6']}
treeCheckedStrategy="all"
/>
);
- assert.deepEqual(getLabels(wrapper), ['裙子', '女装']);
+ assert.deepEqual(getLabels(wrapper), ['女装', '裙子']);
wrapper
.find('div.next-tag')
@@ -567,13 +572,13 @@ describe('TreeSelect', () => {
});
});
-function cloneData(data, keyMap = {}) {
+function cloneData(data, valueMap = {}) {
const loop = data =>
data.map(item => {
let newItem;
- if (item.key in keyMap) {
- newItem = { ...item, ...keyMap[item.key] };
+ if (item.value in valueMap) {
+ newItem = { ...item, ...valueMap[item.value] };
} else {
newItem = { ...item };
}
diff --git a/test/tree/index-spec.js b/test/tree/index-spec.js
index 5c7a78810c..5b3e0b063a 100644
--- a/test/tree/index-spec.js
+++ b/test/tree/index-spec.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import assert from 'power-assert';
import ReactTestUtils from 'react-dom/test-utils';
-import { dom, KEYCODE, func } from '../../src/util';
+import { dom, KEYCODE } from '../../src/util';
import Tree from '../../src/tree/index';
import '../../src/tree/style.js';
@@ -111,7 +111,9 @@ class CheckDemo extends Component {
defaultExpandAll
checkable
checkedKeys={this.state.checkedKeys}
- dataSource={dataSource}
+ dataSource={cloneData(dataSource, {
+ 2: { disabled: false }
+ })}
onCheck={this.handleCheck}
{...this.props}
/>
@@ -411,8 +413,8 @@ describe('Tree', () => {
);
assertChecked('3', true);
assertChecked('6', true);
- assertChecked('1', false);
- assertIndeterminate('1', true);
+ assertChecked('1', true);
+ assertIndeterminate('1', false);
assert(
hasClass(
findTreeNodeByKey('4').querySelector('.next-checkbox-wrapper'),
@@ -452,7 +454,9 @@ describe('Tree', () => {
,
mountNode
@@ -474,7 +478,9 @@ describe('Tree', () => {
checkable
checkedStrategy="parent"
defaultExpandAll
- dataSource={dataSource}
+ dataSource={cloneData(dataSource, {
+ 2: { disabled: false }
+ })}
onCheck={handleCheck}
/>,
mountNode
@@ -704,6 +710,47 @@ describe('Tree', () => {
assert(hasClass(findTreeNodeByKey('1'), 'next-filtered'));
});
+ it('should support disabled', () => {
+ ReactDOM.render(
+ ,
+ mountNode
+ );
+
+ ['1', '3', '6'].forEach(key => assertChecked(key, true));
+ checkTreeNode('6');
+ checkTreeNode('4');
+ ['1', '3', '6'].forEach(key => assertChecked(key, false));
+ assertChecked('4', true);
+ })
+
+ it('should support checkable = false', () => {
+ ReactDOM.render(
+ ,
+ mountNode
+ );
+ ['3', '6'].forEach(key => assertChecked(key, true));
+ assertIndeterminate('1', true);
+ checkTreeNode('4');
+ checkTreeNode('5');
+ ['1', '3', '6'].forEach(key => assertChecked(key, true));
+ assertIndeterminate('1', false);
+ });
+
it('should support keyboard', () => {
ReactDOM.render(