Skip to content

Commit

Permalink
feat: 显示所有选中节点(包括父节点) (#2233)
Browse files Browse the repository at this point in the history
* feat: 显示所有选中节点(包括父节点)

* feat: 显示所有选中节点(包括父节点)

* feat: 更新文档

* feat: Tree/TreeSelect add autoMergeValue API

* chore: update yarn.lock file

* chore: delete console.log

* chore: Remove unnecessary parameters

---------

Co-authored-by: zhangyumei.0319 <[email protected]>
  • Loading branch information
LuyangFE and YyumeiZhang authored Jun 17, 2024
1 parent 0d1719d commit 29de7e5
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 43 deletions.
1 change: 1 addition & 0 deletions content/input/treeselect/index-en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,7 @@ function Demo() {
| arrowIcon|Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon| ReactNode | | 1.15.0|
|autoAdjustOverflow|Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body)|boolean | true| 0.34.0|
| autoExpandParent | Toggle whether to expand parent nodes automatically | boolean | false | 0.34.0 |
| autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, value will include that node and its children. (Works if leafOnly is false)| boolean | false | 2.60.0 |
| borderless | borderless mode >=2.33.0 | boolean | |
| checkRelation | In multiple, the relationship between the checked states of the nodes, optional: 'related'、'unRelated' | string | 'related' | 2.5.0 |
| className | Class name | string | - | - |
Expand Down
1 change: 1 addition & 0 deletions content/input/treeselect/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,7 @@ function Demo() {
| arrowIcon| 自定义右侧下拉箭头Icon,当showClear开关打开且当前有选中值时,hover会优先显示clear icon | ReactNode | | 1.15.0|
| autoAdjustOverflow| 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body) |boolean | true| 0.34.0|
| autoExpandParent | 是否自动展开父节点 | boolean | false | 0.34.0 |
| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将包括该节点以及该子孙节点。(在leafOnly为false的情况下生效)| boolean | false | 2.60.0 |
| borderless | 无边框模式 >=2.33.0 | boolean | |
| checkRelation | 多选时,节点之间选中状态的关系,可选:'related'、'unRelated' | string | 'related' | 2.5.0 |
| className | 选择框的 `className` 属性 | string | - | - |
Expand Down
1 change: 1 addition & 0 deletions content/navigation/tree/index-en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -2271,6 +2271,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
| ------------------- | --------------------- | ------------------------------------------------- | ------- | ------ |
| autoExpandParent | Toggle whether to expand parent node automatically | boolean | false | 0.34.0 |
| autoExpandWhenDragEnter | Toggle whether allow autoExpand when drag enter node | boolean | true | 1.8.0 |
| autoMergeValue | Sets the automerge value. Specifically, when enabled, when a parent node is selected, value will include that node and its children. (Works if leafOnly is false)| boolean | false | 2.60.0 |
| blockNode | Toggle whether to display node as row | boolean | true | - |
| checkRelation | In multiple, the relationship between the checked states of the nodes, optional: 'related'、'unRelated' | string | 'related' | 2.5.0 |
| className | Class name| string | - | - |
Expand Down
1 change: 1 addition & 0 deletions content/navigation/tree/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,7 @@ import { IconFixedStroked, IconSectionStroked, IconAbsoluteStroked, IconInnerSec
|------------- | ----------- | -------------- | -------------- | --------|
| autoExpandParent | 是否自动展开父节点,默认为 false,当组件初次挂载时为 true | boolean | false | 0.34.0 |
| autoExpandWhenDragEnter | 是否允许拖拽到节点上时自动展开改节点 | boolean | true | 1.8.0 |
| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将包括该节点以及该子孙节点。(在leafOnly为false的情况下生效)| boolean | false | 2.60.0 |
| blockNode | 行显示节点 | boolean | true | - |
| checkRelation | 多选时,节点之间选中状态的关系,可选:'related'、'unRelated' | string | 'related' | 2.5.0 |
| className | 类名 | string | - | - |
Expand Down
4 changes: 2 additions & 2 deletions packages/semi-foundation/tree/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,11 @@ export default class TreeFoundation extends BaseFoundation<TreeAdapter, BasicTre

notifyMultipleChange(key: string[], e: any) {
const { keyEntities } = this.getStates();
const { leafOnly, checkRelation, keyMaps } = this.getProps();
const { leafOnly, checkRelation, keyMaps, autoMergeValue } = this.getProps();
let value;
let keyList = [];
if (checkRelation === 'related') {
keyList = normalizeKeyList(key, keyEntities, leafOnly, true);
keyList = autoMergeValue ? normalizeKeyList(key, keyEntities, leafOnly, true) : key;
} else if (checkRelation === 'unRelated') {
keyList = key;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/semi-foundation/treeSelect/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,10 +399,10 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st

_notifyMultipleChange(key: string[], e: any) {
const { keyEntities } = this.getStates();
const { leafOnly, checkRelation, keyMaps } = this.getProps();
const { leafOnly, checkRelation, keyMaps, autoMergeValue } = this.getProps();
let keyList = [];
if (checkRelation === 'related') {
keyList = normalizeKeyList(key, keyEntities, leafOnly, true);
keyList = autoMergeValue ? normalizeKeyList(key, keyEntities, leafOnly, true) : key;
} else if (checkRelation === 'unRelated') {
keyList = key as string[];
}
Expand Down
21 changes: 21 additions & 0 deletions packages/semi-ui/tree/__test__/treeMultiple.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,4 +821,25 @@ describe('Tree', () => {
expect(spyOnChange.calledWithMatch(['fish', 'Yazhou'])).toEqual(true);
tree.unmount();
})

it('onChange + autoMergeValue', () => {
let spyOnSelect = sinon.spy(() => { });
let spyOnChange = sinon.spy(() => { });
let treeSelect = getTree({
defaultExpandAll: true,
onSelect: spyOnSelect,
onChange: spyOnChange,
autoMergeValue: false,
});
let nodeChina = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`).at(0);
// select China
nodeChina.simulate('click');
// onSelect & onChange
expect(spyOnSelect.calledOnce).toBe(true);
expect(spyOnChange.calledOnce).toBe(true);
expect(spyOnSelect.calledWithMatch('zhongguo', true, { key: "zhongguo" })).toEqual(true);
expect(spyOnChange.calledWithMatch(
['Zhongguo', 'Beijing', 'Shanghai'],
)).toEqual(true);
});
})
22 changes: 22 additions & 0 deletions packages/semi-ui/tree/_story/tree.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3116,4 +3116,26 @@ export const CustomRenderIcon = () => {
treeData={treeDataWithSuffix}
icon={iconFunc}
/>);
}

export const AutoMerge = () => {
const [value, setValue] = useState([]);

const onChange = useCallback((val) => {
console.log('onChange', val);
setValue(val);
}, []);

return (
<>
<Tree
autoMergeValue={false}
style={{ width: 300}}
multiple
value={value}
onChange={onChange}
treeData={treeData1}
/>
</>
)
}
2 changes: 2 additions & 0 deletions packages/semi-ui/tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
static contextType = ConfigContext;

static propTypes = {
autoMergeValue: PropTypes.bool,
blockNode: PropTypes.bool,
className: PropTypes.string,
showClear: PropTypes.bool,
Expand Down Expand Up @@ -140,6 +141,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
draggable: false,
autoExpandWhenDragEnter: true,
checkRelation: 'related',
autoMergeValue: true,
};

static TreeNode: typeof TreeNode;
Expand Down
3 changes: 2 additions & 1 deletion packages/semi-ui/tree/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export interface TreeProps extends BasicTreeProps {
onSelect?: (selectedKey: string, selected: boolean, selectedNode: TreeNodeData) => void;
renderDraggingNode?: (nodeInstance: HTMLElement, node: TreeNodeData) => HTMLElement;
renderFullLabel?: (renderFullLabelProps: RenderFullLabelProps) => ReactNode;
renderLabel?: (label?: ReactNode, treeNode?: TreeNodeData) => ReactNode
renderLabel?: (label?: ReactNode, treeNode?: TreeNodeData) => ReactNode;
autoMergeValue?: boolean
}
export interface OptionProps {
index: number;
Expand Down
24 changes: 24 additions & 0 deletions packages/semi-ui/treeSelect/__test__/treeMultiple.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1129,4 +1129,28 @@ describe('TreeSelect', () => {
let selectContentNode = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-selection`).at(0);
expect(selectContentNode.find(`.${BASE_CLASS_PREFIX}-tag-content`).instance().textContent).toEqual('中国');
});

it('onChange + autoMergeValue', () => {
let spyOnSelect = sinon.spy(() => { });
let spyOnChange = sinon.spy(() => { });
let treeSelect = getTreeSelect({
defaultExpandAll: true,
onSelect: spyOnSelect,
onChange: spyOnChange,
autoMergeValue: false,
});
let nodeChina = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`).at(0);
// select China
nodeChina.simulate('click');
// onSelect & onChange
expect(spyOnSelect.calledOnce).toBe(true);
expect(spyOnChange.calledOnce).toBe(true);
expect(spyOnSelect.calledWithMatch('zhongguo', true, { key: "zhongguo" })).toEqual(true);
expect(spyOnChange.calledWithMatch(
['Zhongguo', 'Beijing', 'Shanghai'],
)).toEqual(true);

let tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
expect(tagGroup.length).toEqual(3);
});
})
22 changes: 22 additions & 0 deletions packages/semi-ui/treeSelect/_story/treeSelect.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2791,3 +2791,25 @@ export const CustomSelectAll = () => {
</>
);
};

export const AutoMerge = () => {
const [value, setValue] = useState([]);

const onChange = useCallback((val) => {
console.log('onChange', val);
setValue(val);
}, []);

return (
<>
<TreeSelect
autoMergeValue={false}
style={{ width: 300}}
multiple
value={value}
onChange={onChange}
treeData={treeData1}
/>
</>
)
}
64 changes: 26 additions & 38 deletions packages/semi-ui/treeSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ export interface TreeSelectProps extends Omit<BasicTreeSelectProps, OverrideComm
onChange?: OnChange;
onFocus?: (e: React.MouseEvent) => void;
onVisibleChange?: (isVisible: boolean) => void;
onClear?: (e: React.MouseEvent | React.KeyboardEvent<HTMLDivElement>) => void
onClear?: (e: React.MouseEvent | React.KeyboardEvent<HTMLDivElement>) => void;
autoMergeValue?: boolean
}

export type OverrideCommonState =
Expand Down Expand Up @@ -271,6 +272,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
restTagsPopoverProps: PropTypes.object,
preventScroll: PropTypes.bool,
clickTriggerToHide: PropTypes.bool,
autoMergeValue: PropTypes.bool,
};

static defaultProps: Partial<TreeSelectProps> = {
Expand Down Expand Up @@ -304,6 +306,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
showRestTagsPopover: false,
restTagsPopoverProps: {},
clickTriggerToHide: true,
autoMergeValue: true,
};
inputRef: React.RefObject<typeof Input>;
tagInputRef: React.RefObject<TagInput>;
Expand Down Expand Up @@ -859,15 +862,14 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
return showClear && (this.hasValue() || triggerSearchHasInputValue) && !disabled && (isOpen || isHovering);
};

renderTagList = () => {
const { checkedKeys, keyEntities, disabledKeys, realCheckedKeys } = this.state;
renderTagList = (triggerRenderKeys: string[]) => {
const { keyEntities, disabledKeys } = this.state;
const {
treeNodeLabelProp,
leafOnly,
disabled,
disableStrictly,
size,
checkRelation,
renderSelectedItem: propRenderSelectedItem,
keyMaps
} = this.props;
Expand All @@ -878,14 +880,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
isRenderInTag: true,
content: get(item, realLabelName, null)
});
let renderKeys = [];
if (checkRelation === 'related') {
renderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly, true);
} else if (checkRelation === 'unRelated' && Object.keys(keyEntities).length > 0) {
renderKeys = [...realCheckedKeys];
}
const tagList: Array<React.ReactNode> = [];
renderKeys.forEach((key: TreeNodeData['key'], index) => {
triggerRenderKeys.forEach((key: TreeNodeData['key'], index) => {
const item = (keyEntities[key] && keyEntities[key].key === key) ? keyEntities[key].data : this.getDataForKeyNotInKeyEntities(key);
const onClose = (tagContent: any, e: React.MouseEvent) => {
if (e && typeof e.preventDefault === 'function') {
Expand Down Expand Up @@ -950,7 +946,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
);
};

renderSelectContent = () => {
renderSelectContent = (triggerRenderKeys: string[]) => {
const {
multiple,
placeholder,
Expand All @@ -963,7 +959,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
const isTriggerPositionSearch = filterTreeNode && searchPosition === strings.SEARCH_POSITION_TRIGGER;
// searchPosition = trigger
if (isTriggerPositionSearch) {
return multiple ? this.renderTagInput() : this.renderSingleTriggerSearch();
return multiple ? this.renderTagInput(triggerRenderKeys) : this.renderSingleTriggerSearch();
}
// searchPosition = dropdown and single seleciton
if (!multiple || !this.hasValue()) {
Expand All @@ -974,7 +970,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
return <span className={spanCls}>{renderText ? renderText : placeholder}</span>;
}
// searchPosition = dropdown and multiple seleciton
const tagList = this.renderTagList();
const tagList = this.renderTagList(triggerRenderKeys);
// mode=custom to return tagList directly
return (
<TagGroup<'custom'>
Expand Down Expand Up @@ -1071,6 +1067,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
searchPosition,
triggerRender,
borderless,
autoMergeValue,
checkRelation,
...rest
} = this.props;
Expand Down Expand Up @@ -1110,17 +1107,19 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
className
);
let inner: React.ReactNode | React.ReactNode[];
if (useCustomTrigger) {
let triggerRenderKeys = [];
if (multiple) {
if (checkRelation === 'related') {
triggerRenderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly, true);
} else if (checkRelation === 'unRelated') {
triggerRenderKeys = [...realCheckedKeys];
}
} else {
triggerRenderKeys = selectedKeys;
let triggerRenderKeys = [];
if (multiple) {
if (!autoMergeValue) {
triggerRenderKeys =[...checkedKeys];
} else if (checkRelation === 'related') {
triggerRenderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly, true);
} else if (checkRelation === 'unRelated') {
triggerRenderKeys = [...realCheckedKeys];
}
} else {
triggerRenderKeys = selectedKeys;
}
if (useCustomTrigger) {
inner = <Trigger
inputValue={inputValue}
value={triggerRenderKeys.map((key: string) => get(keyEntities, [key, 'data']))}
Expand All @@ -1137,7 +1136,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
inner = [
<Fragment key={'prefix'}>{prefix || insetLabel ? this.renderPrefix() : null}</Fragment>,
<Fragment key={'selection'}>
<div className={`${prefixcls}-selection`}>{this.renderSelectContent()}</div>
<div className={`${prefixcls}-selection`}>{this.renderSelectContent(triggerRenderKeys)}</div>
</Fragment>,
<Fragment key={'suffix'}>{suffix ? this.renderSuffix() : null}</Fragment>,
<Fragment key={'clearBtn'}>
Expand Down Expand Up @@ -1238,33 +1237,22 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
);
};

renderTagInput = () => {
renderTagInput = (triggerRenderKeys: string[]) => {
const {
leafOnly,
disabled,
size,
searchAutoFocus,
placeholder,
maxTagCount,
checkRelation,
showRestTagsPopover,
restTagsPopoverProps,
searchPosition,
filterTreeNode,
preventScroll
} = this.props;
const {
keyEntities,
checkedKeys,
inputValue,
realCheckedKeys,
} = this.state;
let keyList = [];
if (checkRelation === 'related') {
keyList = normalizeKeyList(checkedKeys, keyEntities, leafOnly, true);
} else if (checkRelation === 'unRelated') {
keyList = [...realCheckedKeys];
}
// auto focus search input divide into two parts
// 1. filterTreeNode && searchPosition === strings.SEARCH_POSITION_TRIGGER
// Implemented by passing autofocus to the underlying input's autofocus
Expand All @@ -1280,7 +1268,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
onInputChange={v => this.search(v)}
ref={this.tagInputRef}
placeholder={placeholder}
value={keyList}
value={triggerRenderKeys}
inputValue={inputValue}
size={size}
showRestTagsPopover={showRestTagsPopover}
Expand Down

0 comments on commit 29de7e5

Please sign in to comment.