Skip to content

Commit 04abd78

Browse files
lakerswgqgliudong
andauthored
feat(TreeSelect): support preserveNonExistentValue props (alibaba-fusion#3424), close alibaba-fusion#3375
Co-authored-by: gliudong <g.liudong@gmail.com>
1 parent 20fbe62 commit 04abd78

File tree

7 files changed

+230
-54
lines changed

7 files changed

+230
-54
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# value 不存在
2+
3+
- order: 5
4+
5+
通过设置 `preserveNonExistentValue`,可以让 value 在 dataSource 中不存在时仍然显示
6+
7+
:::lang=en-us
8+
9+
# non-existent-value
10+
11+
- order: 0
12+
13+
display value even though value not exist in dataSource
14+
:::
15+
16+
---
17+
18+
```jsx
19+
import { TreeSelect } from '@alifd/next';
20+
21+
const treeData = [
22+
{
23+
label: 'Component',
24+
value: '1',
25+
children: [
26+
{
27+
label: 'Form',
28+
value: '2',
29+
children: [
30+
{
31+
label: 'Input',
32+
value: '4',
33+
},
34+
{
35+
label: 'Select',
36+
value: '5',
37+
},
38+
],
39+
},
40+
{
41+
label: 'Display',
42+
value: '3',
43+
children: [
44+
{
45+
label: 'Table',
46+
value: '6',
47+
},
48+
],
49+
},
50+
],
51+
},
52+
];
53+
54+
class Demo extends React.Component {
55+
constructor(props) {
56+
super(props);
57+
58+
this.handleChange = this.handleChange.bind(this);
59+
}
60+
61+
handleChange(value, data) {
62+
console.log(value, data);
63+
}
64+
65+
render() {
66+
return (
67+
<TreeSelect
68+
value={{value: 'non-existent', label: 'not exist'}}
69+
preserveNonExistentValue
70+
treeDefaultExpandAll
71+
treeCheckable
72+
dataSource={treeData}
73+
onChange={this.handleChange}
74+
style={{ width: 200 }}
75+
/>
76+
);
77+
}
78+
}
79+
80+
ReactDOM.render(<Demo />, mountNode);
81+
```

docs/tree-select/index.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Like Select, TreeSelect can be used when the selected data structure is a tree s
5252
| popupProps | properties of Popup | Object | - |
5353
| followTrigger | follow Trigger or not | Boolean | - |
5454
| useVirtual | whether use virtual scroll | Boolean | false |
55+
| preserveNonExistentValue | if reserve value when value/defaultValue not exist in dataSource | Boolean | false | 1.25 |
5556

5657
<!-- api-extra-start -->
5758

docs/tree-select/index.md

+40-39
Large diffs are not rendered by default.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"fusion design",
88
"next",
99
"component",
10-
"ui tookit",
10+
"ui toolkit",
1111
"react",
1212
"react-components",
1313
"components",

src/tree-select/tree-select.jsx

+87-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, { Component, Children, isValidElement, cloneElement } from 'react';
22
import { polyfill } from 'react-lifecycles-compat';
33
import PropTypes from 'prop-types';
4-
import cloneDeep from 'lodash.clonedeep';
54
import classNames from 'classnames';
65
import Select from '../select';
76
import Tree from '../tree';
@@ -13,6 +12,7 @@ import {
1312
isDescendantOrSelf,
1413
} from '../tree/view/util';
1514
import { func, obj, KEYCODE } from '../util';
15+
import { getValueDataSource, valueToSelectKey } from '../select/util';
1616

1717
const noop = () => {};
1818
const { Node: TreeNode } = Tree;
@@ -165,14 +165,19 @@ class TreeSelect extends Component {
165165
* 数据源,该属性优先级高于 children
166166
*/
167167
dataSource: PropTypes.arrayOf(PropTypes.object),
168+
/**
169+
* value/defaultValue 在 dataSource 中不存在时,是否展示
170+
* @version 1.25
171+
*/
172+
preserveNonExistentValue: PropTypes.bool,
168173
/**
169174
* (受控)当前值
170175
*/
171-
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
176+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.arrayOf(PropTypes.any)]),
172177
/**
173178
* (非受控)默认值
174179
*/
175-
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
180+
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.arrayOf(PropTypes.any)]),
176181
/**
177182
* 选中值改变时触发的回调函数
178183
* @param {String|Array} value 选中的值,单选时返回单个值,多选时返回数组
@@ -306,12 +311,18 @@ class TreeSelect extends Component {
306311
defaultVisible: false,
307312
onVisibleChange: noop,
308313
useVirtual: false,
314+
/**
315+
* TODO
316+
* 目前 select/cascade select 是默认支持的,在 2.x 版本中 tree-select 也将默认支持
317+
*/
318+
preserveNonExistentValue: false,
309319
};
310320

311321
constructor(props, context) {
312322
super(props, context);
313323

314324
const { defaultVisible, visible, defaultValue, value } = props;
325+
315326
this.state = {
316327
visible: typeof visible === 'undefined' ? defaultVisible : visible,
317328
value: normalizeToArray(typeof value === 'undefined' ? defaultValue : value),
@@ -320,9 +331,19 @@ class TreeSelect extends Component {
320331
searchedKeys: [],
321332
retainedKeys: [],
322333
autoExpandParent: false,
334+
// map of value => item, includes value not exist in dataSource
335+
mapValueDS: {},
323336
...flatDataSource(props),
324337
};
325338

339+
// init value/mapValueDS when defaultValue is not undefined
340+
if (this.state.value !== undefined) {
341+
this.state.mapValueDS = getValueDataSource(this.state.value, this.state.mapValueDS).mapValueDS;
342+
this.state.value = this.state.value.map(v => {
343+
return valueToSelectKey(v);
344+
});
345+
}
346+
326347
bindCtx(this, [
327348
'handleSelect',
328349
'handleCheck',
@@ -342,7 +363,13 @@ class TreeSelect extends Component {
342363
const st = {};
343364

344365
if ('value' in props) {
345-
st.value = normalizeToArray(props.value);
366+
const valueArray = normalizeToArray(props.value);
367+
// convert value to string[]
368+
st.value = valueArray.map(v => {
369+
return valueToSelectKey(v);
370+
});
371+
// re-calculate map
372+
st.mapValueDS = getValueDataSource(props.value, state.mapValueDS).mapValueDS;
346373
}
347374
if ('visible' in props) {
348375
st.visible = props.visible;
@@ -370,7 +397,6 @@ class TreeSelect extends Component {
370397
if (k) {
371398
ret.push(k);
372399
}
373-
374400
return ret;
375401
}, []);
376402
}
@@ -381,6 +407,7 @@ class TreeSelect extends Component {
381407

382408
getValueForSelect(value) {
383409
const { treeCheckedStrategy } = this.props;
410+
const nonExistentValueKeys = this.getNonExistentValueKeys();
384411

385412
let keys = this.getKeysByValue(value);
386413
keys = getAllCheckedKeys(keys, this.state._k2n, this.state._p2n);
@@ -396,11 +423,16 @@ class TreeSelect extends Component {
396423
break;
397424
}
398425

399-
return this.getValueByKeys(keys);
426+
const values = this.getValueByKeys(keys);
427+
428+
return [...values, ...nonExistentValueKeys];
400429
}
401430

402431
getData(value, forSelect) {
403-
return value.reduce((ret, v) => {
432+
const { preserveNonExistentValue } = this.props;
433+
const { mapValueDS } = this.state;
434+
435+
const ret = value.reduce((ret, v) => {
404436
const k = this.state._v2n[v] && this.state._v2n[v].key;
405437
if (k) {
406438
const { label, pos, disabled, checkboxDisabled } = this.state._k2n[k];
@@ -415,10 +447,38 @@ class TreeSelect extends Component {
415447
d.key = k;
416448
}
417449
ret.push(d);
450+
} else if (preserveNonExistentValue) {
451+
// 需要保留 dataSource 中不存在的 value
452+
const item = mapValueDS[v];
453+
if (item) {
454+
ret.push(item);
455+
}
418456
}
419-
420457
return ret;
421458
}, []);
459+
460+
return ret;
461+
}
462+
463+
getNonExistentValues() {
464+
const { preserveNonExistentValue } = this.props;
465+
const { value } = this.state;
466+
467+
if (!preserveNonExistentValue) {
468+
return [];
469+
}
470+
const nonExistentValues = value.filter(v => !this.state._v2n[v]);
471+
return nonExistentValues;
472+
}
473+
474+
getNonExistentValueKeys() {
475+
const nonExistentValues = this.getNonExistentValues();
476+
return nonExistentValues.map(v => {
477+
if (typeof v === 'object' && v.hasOwnProperty('value')) {
478+
return v.value;
479+
}
480+
return v;
481+
});
422482
}
423483

424484
saveTreeRef(ref) {
@@ -448,7 +508,10 @@ class TreeSelect extends Component {
448508
const { selected } = extra;
449509

450510
if (multiple || selected) {
451-
const value = this.getValueByKeys(selectedKeys);
511+
let value = this.getValueByKeys(selectedKeys);
512+
const nonExistentValues = this.getNonExistentValues();
513+
value = [...nonExistentValues, ...value];
514+
452515
if (!('value' in this.props)) {
453516
this.setState({
454517
value,
@@ -468,7 +531,10 @@ class TreeSelect extends Component {
468531
handleCheck(checkedKeys) {
469532
const { onChange } = this.props;
470533

471-
const value = this.getValueByKeys(checkedKeys);
534+
let value = this.getValueByKeys(checkedKeys);
535+
const nonExistentValues = this.getNonExistentValues();
536+
value = [...nonExistentValues, ...value];
537+
472538
if (!('value' in this.props)) {
473539
this.setState({
474540
value,
@@ -483,7 +549,14 @@ class TreeSelect extends Component {
483549
const { treeCheckable, treeCheckStrictly, treeCheckedStrategy, onChange } = this.props;
484550

485551
let value;
486-
if (treeCheckable && !treeCheckStrictly && ['parent', 'all'].indexOf(treeCheckedStrategy) !== -1) {
552+
if (
553+
// there's linkage relationship among nodes
554+
treeCheckable &&
555+
!treeCheckStrictly &&
556+
['parent', 'all'].indexOf(treeCheckedStrategy) !== -1 &&
557+
// value exits in datasource
558+
this.state._v2n[removedValue]
559+
) {
487560
const removedPos = this.state._v2n[removedValue].pos;
488561
value = this.state.value.filter(v => {
489562
const p = this.state._v2n[v].pos;
@@ -804,9 +877,11 @@ class TreeSelect extends Component {
804877
isPreview,
805878
} = this.props;
806879
const others = pickOthers(Object.keys(TreeSelect.propTypes), this.props);
807-
const { value, visible } = this.state;
880+
const { visible, value } = this.state;
808881

882+
// if (non-leaf 节点可选 & 父子节点选中状态需要联动),需要额外计算父子节点间的联动关系
809883
const valueForSelect = treeCheckable && !treeCheckStrictly ? this.getValueForSelect(value) : value;
884+
810885
let data = this.getData(valueForSelect, true);
811886
if (!multiple && !treeCheckable) {
812887
data = data[0];

test/tree-select/index-spec.js

+13
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ describe('TreeSelect', () => {
141141
assertDataAndNodes(newDataSource);
142142
});
143143

144+
144145
it('should render by defaultValue', () => {
145146
wrapper = mount(<TreeSelect defaultValue="4" defaultVisible treeDefaultExpandAll dataSource={dataSource} />);
146147
assertSelected('4', true);
@@ -344,6 +345,17 @@ describe('TreeSelect', () => {
344345
assert(triggered);
345346
});
346347

348+
it('should render tag when defaultValue [{ label, value}]', () => {
349+
wrapper = mount(<TreeSelect
350+
defaultValue={[{ label: 'test1', value: '123'}]}
351+
treeDefaultExpandAll
352+
treeCheckable
353+
preserveNonExistentValue
354+
dataSource={dataSource} />);
355+
356+
assert.deepEqual(getLabels(wrapper), ['test1']);
357+
});
358+
347359
it('should render parent tag when set treeCheckedStrategy to all', () => {
348360
wrapper = mount(
349361
<TreeSelect
@@ -717,6 +729,7 @@ describe('TreeSelect', () => {
717729
done();
718730
}, 2000);
719731
});
732+
720733
});
721734

722735
function cloneData(data, valueMap = {}) {

types/tree-select/index.d.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,17 @@ export interface TreeSelectProps extends HTMLAttributesWeak, CommonProps {
6969
/**
7070
* (受控)当前值
7171
*/
72-
value?: string | Array<any>;
72+
value?: string | object | Array<any>;
7373

7474
/**
7575
* (非受控)默认值
7676
*/
77-
defaultValue?: string | Array<any>;
77+
defaultValue?: string | object | Array<any>;
78+
79+
/**
80+
* value/defaultValue 在 dataSource 中不存在时,是否展示
81+
*/
82+
preserveNonExistentValue?: boolean;
7883

7984
/**
8085
* 选中值改变时触发的回调函数

0 commit comments

Comments
 (0)