Skip to content

Commit 8180d02

Browse files
authored
feat(cascader): support parent node filtering (#3763)
* feat(cascader): support parentnode filtering * chore: demo * chore: update snapshot * chore: docs * chore: docs
1 parent 1769464 commit 8180d02

File tree

16 files changed

+273
-79
lines changed

16 files changed

+273
-79
lines changed

src/cascader/_example-composition/custom-options.vue

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<t-space>
2+
<t-space direction="vertical">
33
<!-- 方式一:使用 options 自定义下拉选项内容 -->
44
<t-cascader
55
v-model="value1"
@@ -33,6 +33,22 @@
3333
style="width: 300px"
3434
>
3535
</t-cascader>
36+
<t-cascader
37+
v-model="value4"
38+
:popup-props="{ overlayClassName: 'tdesign-demo-select__overlay-option' }"
39+
:options="options"
40+
multiple
41+
>
42+
<template #option="{ item, onChange }">
43+
<div class="tdesign-demo__user-option" @click="(e) => handleClick(item, onChange)">
44+
<img src="https://tdesign.gtimg.com/site/avatar.jpg" />
45+
<div class="tdesign-demo__user-option-info">
46+
<div>{{ item.label }}</div>
47+
<div>{{ item.value }}</div>
48+
</div>
49+
</div>
50+
</template>
51+
</t-cascader>
3652
</t-space>
3753
</template>
3854

@@ -42,6 +58,7 @@ import { ref, computed } from 'vue';
4258
const value1 = ref('');
4359
const value2 = ref('');
4460
const value3 = ref('');
61+
const value4 = ref([]);
4562
const options = ref([
4663
{
4764
label: '选项一',
@@ -97,6 +114,12 @@ const getDeepOptions = (options) => {
97114
}),
98115
}));
99116
};
117+
118+
const handleClick = (item, changeCallback) => {
119+
if (!Array.isArray(item.children)) {
120+
changeCallback();
121+
}
122+
};
100123
</script>
101124

102125
<style>

src/cascader/_example-composition/filterable.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<t-cascader v-model="value" :options="options" filterable clearable />
44
<t-cascader v-model="value2" :options="options" filterable clearable multiple :min-collapsed-num="2" />
55
<t-cascader v-model="value3" :filter="filterMethod" :options="options" clearable :min-collapsed-num="2" />
6+
<t-cascader v-model="value4" filterable :options="options" clearable multiple check-strictly />
67
</t-space>
78
</template>
89
<script setup>

src/cascader/_example/custom-options.vue

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<t-space>
2+
<t-space direction="vertical">
33
<!-- 方式一:使用 options 自定义下拉选项内容 -->
44
<t-cascader
55
v-model="value1"
@@ -9,10 +9,10 @@
99
/>
1010
<!-- 方式二:使用插槽自定义下拉选项内容 -->
1111
<t-cascader
12-
v-model="value2"
1312
:popupProps="{ overlayClassName: 'tdesign-demo-select__overlay-option' }"
1413
:options="options"
1514
style="width: 300px"
15+
multiple
1616
>
1717
<template v-slot:option="{ item }">
1818
<div class="tdesign-demo__user-option">
@@ -33,6 +33,22 @@
3333
style="width: 300px"
3434
>
3535
</t-cascader>
36+
<t-cascader
37+
v-model="value4"
38+
:popup-props="{ overlayClassName: 'tdesign-demo-select__overlay-option' }"
39+
:options="options"
40+
multiple
41+
>
42+
<template #option="{ item, onChange }">
43+
<div class="tdesign-demo__user-option" @click="(e) => handleClick(item, onChange)">
44+
<img src="https://tdesign.gtimg.com/site/avatar.jpg" />
45+
<div class="tdesign-demo__user-option-info">
46+
<div>{{ item.label }}</div>
47+
<div>{{ item.value }}</div>
48+
</div>
49+
</div>
50+
</template>
51+
</t-cascader>
3652
</t-space>
3753
</template>
3854

@@ -43,6 +59,7 @@ export default {
4359
value1: '',
4460
value2: '',
4561
value3: '',
62+
value4: [],
4663
options: [
4764
{
4865
label: '选项一',
@@ -105,6 +122,11 @@ export default {
105122
</div>
106123
);
107124
},
125+
handleClick(item, changeCallback) {
126+
if (!Array.isArray(item.children)) {
127+
changeCallback();
128+
}
129+
},
108130
},
109131
};
110132
</script>

src/cascader/_example/filterable.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<t-cascader v-model="value" :options="options" filterable clearable />
44
<t-cascader v-model="value2" :options="options" filterable clearable multiple :min-collapsed-num="2" />
55
<t-cascader v-model="value3" :filter="filterMethod" :options="options" clearable :min-collapsed-num="2" />
6+
<t-cascader v-model="value4" filterable :options="options" clearable multiple check-strictly />
67
</t-space>
78
</template>
89
<script>
@@ -46,6 +47,7 @@ export default {
4647
value: '',
4748
value2: ['1.1'],
4849
value3: '',
50+
value4: [],
4951
};
5052
},
5153
methods: {

src/cascader/cascader.en-US.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ loadingText | String / Slot / Function | - | Typescript:`string \| TNode`。[s
2626
max | Number | 0 | \- | N
2727
minCollapsedNum | Number | 0 | \- | N
2828
multiple | Boolean | false | \- | N
29-
option | Slot / Function | - | customize one option。Typescript:`TNode<{ item: CascaderOption; index: number }>`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
29+
option | Slot / Function | - | customize one option。Typescript:`TNode<{ item: CascaderOption; index: number; onChange: ()=> void; onExpand: ()=> void }>`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
3030
options | Array | [] | Typescript:`Array<CascaderOption>` | N
3131
placeholder | String | undefined | \- | N
3232
popupProps | Object | - | Typescript:`PopupProps`[Popup API Documents](./popup?tab=api)[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/cascader/type.ts) | N
3333
popupVisible | Boolean | - | \- | N
3434
defaultPopupVisible | Boolean | - | uncontrolled property | N
3535
readonly | Boolean | false | \- | N
36-
reserveKeyword | Boolean | false | \- | N
36+
reserveKeyword | Boolean | true | \- | N
3737
selectInputProps | Object | - | Typescript:`SelectInputProps`[SelectInput API Documents](./select-input?tab=api)[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/cascader/type.ts) | N
3838
showAllLevels | Boolean | true | \- | N
3939
size | String | medium | options: large/medium/small。Typescript:`SizeEnum`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N

src/cascader/cascader.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ loadingText | String / Slot / Function | - | 远程加载时显示的文字,
2626
max | Number | 0 | 用于控制多选数量,值为 0 则不限制 | N
2727
minCollapsedNum | Number | 0 | 最小折叠数量,用于多选情况下折叠选中项,超出该数值的选中项折叠。值为 0 则表示不折叠 | N
2828
multiple | Boolean | false | 是否允许多选 | N
29-
option | Slot / Function | - | 自定义单个级联选项。TS 类型:`TNode<{ item: CascaderOption; index: number }>`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
29+
option | Slot / Function | - | 自定义单个级联选项, item 是选项本身的值,index 是下标,onChange 用于触发当前节点选中,onExpand 用于触发当前节点展开。TS 类型:`TNode<{ item: CascaderOption; index: number; onChange: ()=> void; onExpand: ()=> void }>`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
3030
options | Array | [] | 可选项数据源。TS 类型:`Array<CascaderOption>` | N
3131
placeholder | String | undefined | 占位符 | N
3232
popupProps | Object | - | 参考 popup 组件 API。TS 类型:`PopupProps`[Popup API Documents](./popup?tab=api)[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/cascader/type.ts) | N
3333
popupVisible | Boolean | - | 是否显示下拉框 | N
3434
readonly | Boolean | false | 只读状态,值为真会隐藏输入框,且无法打开下拉框 | N
35-
reserveKeyword | Boolean | false | 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 | N
35+
reserveKeyword | Boolean | true | 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 | N
3636
selectInputProps | Object | - | 透传 SelectInput 筛选器输入框组件的全部属性。TS 类型:`SelectInputProps`[SelectInput API Documents](./select-input?tab=api)[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/cascader/type.ts) | N
3737
showAllLevels | Boolean | true | 选中值使用完整路径,输入框在单选时也显示完整路径 | N
3838
size | String | medium | 组件尺寸。可选项:large/medium/small。TS 类型:`SizeEnum`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N

src/cascader/components/Item.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default defineComponent({
9999

100100
function RenderCheckBox(node: TreeNode, cascaderContext: CascaderContextType) {
101101
const {
102-
checkProps, value, max, inputVal,
102+
checkProps, value, max, inputVal, isParentFilterable,
103103
} = cascaderContext;
104104
const label = RenderLabelInner(node, cascaderContext);
105105
return (
@@ -109,7 +109,7 @@ export default defineComponent({
109109
disabled={node.isDisabled() || ((value as TreeNodeValue[]).length >= max && max !== 0)}
110110
name={String(node.value)}
111111
title={inputVal ? getFullPathLabel(node) : node.label}
112-
stopLabelTrigger={!!node.children}
112+
stopLabelTrigger={!!node.children && !isParentFilterable}
113113
onChange={(vale: boolean, { e }: { e: MouseEvent }) => {
114114
e.stopPropagation();
115115
onChange();
@@ -138,6 +138,7 @@ export default defineComponent({
138138
? RenderCheckBox(node, cascaderContext)
139139
: RenderLabelContent(node, cascaderContext))}
140140
{node.children
141+
&& !this.cascaderContext.isParentFilterable
141142
&& (node.loading ? <TLoading class={iconClass} size="small" /> : <ChevronRightIcon class={iconClass} />)}
142143
</li>
143144
);

src/cascader/components/Panel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,14 @@ export default defineComponent({
6161
const optionChild = node.data.content
6262
? getDefaultNode(node.data.content(this.$createElement))
6363
: renderTNodeJSXDefault('option', {
64-
params: { item: node.data, index },
64+
params: {
65+
item: node.data,
66+
index,
67+
onExpand: () => handleExpand(node, 'click'),
68+
onChange: () => {
69+
valueChangeEffect(node, cascaderContext);
70+
},
71+
},
6572
});
6673
return (
6774
<Item

src/cascader/core/className.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ export function getNodeStatusClass(
3434
cascaderContext: CascaderContextType,
3535
) {
3636
const {
37-
checkStrictly, multiple, value, max,
37+
checkStrictly, multiple, value, max, isParentFilterable,
3838
} = cascaderContext;
39-
const expandedActive = (!checkStrictly && node.expanded && (multiple ? !node.isLeaf() : true)) || (checkStrictly && node.expanded);
39+
const expandedActive = (!checkStrictly && node.expanded && (multiple ? !node.isLeaf() : true))
40+
|| (checkStrictly && node.expanded && !isParentFilterable);
4041

4142
const isLeaf = node.isLeaf();
4243

@@ -69,14 +70,14 @@ export function getCascaderItemClass(
6970
STATUS: Record<string, string>,
7071
cascaderContext: CascaderContextType,
7172
) {
72-
const { size } = cascaderContext;
73+
const { size, isParentFilterable } = cascaderContext;
7374
return [
7475
`${prefix}-cascader__item`,
7576
...getNodeStatusClass(node, STATUS, cascaderContext),
7677
SIZE[size],
7778
{
7879
[`${prefix}-cascader__item--with-icon`]: !!node.children,
79-
[`${prefix}-cascader__item--leaf`]: node.isLeaf(),
80+
[`${prefix}-cascader__item--leaf`]: node.isLeaf() || isParentFilterable,
8081
},
8182
];
8283
}

src/cascader/core/effect.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function expendClickEffect(
3030
valueType,
3131
filterable,
3232
inputVal,
33+
isParentFilterable,
3334
} = cascaderContext;
3435

3536
const isDisabled = node.disabled || (multiple && (value as TreeNodeValue[]).length >= max && max !== 0);
@@ -46,7 +47,7 @@ export function expendClickEffect(
4647
setTreeNodes(nodes);
4748

4849
// 多选条件下手动维护expend
49-
if (multiple) {
50+
if (multiple && !isParentFilterable) {
5051
setExpend(expanded);
5152
}
5253
}
@@ -74,7 +75,17 @@ export function expendClickEffect(
7475
*/
7576
export function valueChangeEffect(node: TreeNode, cascaderContext: CascaderContextType) {
7677
const {
77-
disabled, max, inputVal, multiple, setVisible, setValue, treeNodes, treeStore, valueType,
78+
disabled,
79+
max,
80+
inputVal,
81+
multiple,
82+
setVisible,
83+
setValue,
84+
treeNodes,
85+
treeStore,
86+
valueType,
87+
setInputVal,
88+
reserveKeyword,
7889
} = cascaderContext;
7990

8091
if (!node || disabled || node.disabled) {
@@ -117,6 +128,7 @@ export function valueChangeEffect(node: TreeNode, cascaderContext: CascaderConte
117128
.map((item) => item.value));
118129

119130
setValue(resValue, node.checked ? 'uncheck' : 'check', node.getModel());
131+
if (!reserveKeyword) setInputVal('');
120132
}
121133

122134
/**
@@ -188,12 +200,13 @@ export const treeNodesEffect = (
188200
treeStore: CascaderContextType['treeStore'],
189201
setTreeNodes: CascaderContextType['setTreeNodes'],
190202
filter: CascaderContextType['filter'],
203+
isParentFilterable: boolean,
191204
) => {
192205
if (!treeStore) return;
193206
let nodes = [];
194207
if (inputVal) {
195208
const filterMethods = (node: TreeNode) => {
196-
if (!node.isLeaf()) return;
209+
if (!node.isLeaf() && !isParentFilterable) return;
197210
if (isFunction(filter)) {
198211
return filter(`${inputVal}`, node as TreeNodeModel & TreeNode);
199212
}

0 commit comments

Comments
 (0)