Skip to content

Commit 03f559a

Browse files
committed
feat: tree-select add status & placement
1 parent bf1226d commit 03f559a

File tree

11 files changed

+182
-7
lines changed

11 files changed

+182
-7
lines changed

components/tree-select/demo/index.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<virtualScrollVue />
1212
<customTagRenderVue />
1313
<replaceFieldsVue />
14+
<placementVue />
15+
<statusVue />
1416
</demo-sort>
1517
</template>
1618
<script lang="ts">
@@ -25,6 +27,8 @@ import treeLineVue from './tree-line.vue';
2527
import virtualScrollVue from './virtual-scroll.vue';
2628
import customTagRenderVue from './custom-tag-render.vue';
2729
import replaceFieldsVue from './replaceFields.vue';
30+
import placementVue from './placement.vue';
31+
import statusVue from './status.vue';
2832
import CN from '../index.zh-CN.md';
2933
import US from '../index.en-US.md';
3034
import { defineComponent } from 'vue';
@@ -33,6 +37,8 @@ export default defineComponent({
3337
CN,
3438
US,
3539
components: {
40+
placementVue,
41+
statusVue,
3642
Basic,
3743
Multiple,
3844
// TreeData,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<docs>
2+
---
3+
order: 28
4+
title:
5+
zh-CN: 弹出位置
6+
en-US: Popup Placement
7+
---
8+
9+
## zh-CN
10+
11+
可以通过 `placement` 手动指定弹出的位置。
12+
13+
## en-US
14+
15+
You can manually specify the position of the popup via `placement`.
16+
17+
</docs>
18+
19+
<template>
20+
<a-radio-group v-model:value="placement">
21+
<a-radio-button value="topLeft">topLeft</a-radio-button>
22+
<a-radio-button value="topRight">topRight</a-radio-button>
23+
<a-radio-button value="bottomLeft">bottomLeft</a-radio-button>
24+
<a-radio-button value="bottomRight">bottomRight</a-radio-button>
25+
</a-radio-group>
26+
<br />
27+
<br />
28+
<a-tree-select
29+
v-model:value="value"
30+
show-search
31+
:dropdown-style="{ maxHeight: '400px', overflow: 'auto', minWidth: '300px' }"
32+
placeholder="Please select"
33+
allow-clear
34+
tree-default-expand-all
35+
:tree-data="treeData"
36+
:placement="placement"
37+
:dropdown-match-select-width="false"
38+
>
39+
<template #title="{ value: val, title }">
40+
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
41+
<template v-else>{{ title }}</template>
42+
</template>
43+
</a-tree-select>
44+
</template>
45+
<script lang="ts">
46+
import { defineComponent, ref } from 'vue';
47+
import type { TreeSelectProps } from 'ant-design-vue';
48+
export default defineComponent({
49+
setup() {
50+
const placement = ref('topLeft' as const);
51+
const value = ref<string>();
52+
const treeData = ref<TreeSelectProps['treeData']>([
53+
{
54+
title: 'parent 1',
55+
value: 'parent 1',
56+
children: [
57+
{
58+
title: 'parent 1-0',
59+
value: 'parent 1-0',
60+
children: [
61+
{
62+
title: 'my leaf',
63+
value: 'leaf1',
64+
},
65+
{
66+
title: 'your leaf',
67+
value: 'leaf2',
68+
},
69+
],
70+
},
71+
{
72+
title: 'parent 1-1',
73+
value: 'parent 1-1',
74+
},
75+
],
76+
},
77+
]);
78+
return {
79+
placement,
80+
value,
81+
treeData,
82+
};
83+
},
84+
});
85+
</script>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<docs>
2+
---
3+
order: 19
4+
version: 3.3.0
5+
title:
6+
zh-CN: 自定义状态
7+
en-US: Status
8+
---
9+
10+
## zh-CN
11+
12+
使用 `status` 为 DatePicker 添加状态,可选 `error` 或者 `warning`。
13+
14+
## en-US
15+
16+
Add status to DatePicker with `status`, which could be `error` or `warning`.
17+
18+
</docs>
19+
20+
<template>
21+
<a-space direction="vertical" style="width: 100%">
22+
<a-tree-select status="error" style="width: 100%" placeholder="Error" />
23+
<a-tree-select status="warning" style="width: 100%" multiple placeholder="Warning multiple" />
24+
</a-space>
25+
</template>
26+
<script lang="ts">
27+
import { defineComponent } from 'vue';
28+
export default defineComponent({
29+
setup() {
30+
return {};
31+
},
32+
});
33+
</script>

components/tree-select/index.en-US.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ Tree selection control.
3434
| multiple | Support multiple or not, will be `true` when enable `treeCheckable`. | boolean | false | | |
3535
| notFoundContent | Specify content to show when no result matches | slot | `Not Found` | | |
3636
| placeholder | Placeholder of the select input | string\|slot | - | | |
37+
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | 3.3.0 |
3738
| replaceFields | Replace the title,value, key and children fields in treeNode with the corresponding fields in treeData | object | { children:'children', label:'title', value: 'value' } | | 1.6.1 (3.0.0 deprecated) |
3839
| searchPlaceholder | Placeholder of the search input | string\|slot | - | | |
3940
| searchValue(v-model) | work with `search` event to make search value controlled. | string | - | | |
4041
| showCheckedStrategy | The way show selected item in box. **Default:** just show child nodes. **`TreeSelect.SHOW_ALL`:** show all checked treeNodes (include parent treeNode). **`TreeSelect.SHOW_PARENT`:** show checked treeNodes (just show parent treeNode). | enum { TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD | | |
4142
| showSearch | Whether to display a search input in the dropdown menu(valid only in the single mode) | boolean | false | | |
4243
| size | To set the size of the select input, options: `large` `small` | string | 'default' | | |
44+
| status | Set validation status | 'error' \| 'warning' | - | 3.3.0 |
4345
| suffixIcon | The custom suffix icon | VNode \| slot | - | | |
4446
| tagRender | Customize tag render when `multiple` | (props) => slot | - | 3.0 | |
4547
| title | custom title | slot | | 3.0.0 | |
@@ -51,6 +53,7 @@ Tree selection control.
5153
| treeDefaultExpandedKeys | Default expanded treeNodes | string\[] \| number\[] | - | | |
5254
| treeExpandedKeys(v-model) | Set expanded keys | string\[] \| number\[] | - | | |
5355
| treeIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to `true` | boolean | false | | |
56+
| treeLoadedKeys | (Controlled) Set loaded tree nodes, work with `loadData` only | string[] | [] | 3.3.0 |
5457
| treeLine | Show the line. Ref [Tree - showLine](/components/tree/#components-tree-demo-line) | boolean \| object | false | 3.0 | |
5558
| treeNodeFilterProp | Will be used for filtering if `filterTreeNode` returns true | string | 'value' | | |
5659
| treeNodeLabelProp | Will render as content of select | string | 'title' | | |

components/tree-select/index.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ import type { SwitcherIconProps } from '../tree/utils/iconUtil';
2020
import renderSwitcherIcon from '../tree/utils/iconUtil';
2121
import { warning } from '../vc-util/warning';
2222
import { flattenChildren } from '../_util/props-util';
23-
import { useInjectFormItemContext } from '../form/FormItemContext';
23+
import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext';
2424
import type { BaseSelectRef } from '../vc-select';
2525
import type { BaseOptionType, DefaultOptionType } from '../vc-tree-select/TreeSelect';
2626
import type { TreeProps } from '../tree';
27+
import type { SelectCommonPlacement } from '../_util/transition';
28+
import { getTransitionDirection } from '../_util/transition';
29+
import type { InputStatus } from '../_util/statusUtils';
30+
import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils';
2731

2832
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
2933
if (transitionName !== undefined) {
@@ -62,6 +66,8 @@ export function treeSelectProps<
6266
bordered: { type: Boolean, default: undefined },
6367
treeLine: { type: [Boolean, Object] as PropType<TreeProps['showLine']>, default: undefined },
6468
replaceFields: { type: Object as PropType<FieldNames> },
69+
placement: String as PropType<SelectCommonPlacement>,
70+
status: String as PropType<InputStatus>,
6571
'onUpdate:value': { type: Function as PropType<(value: any) => void> },
6672
'onUpdate:treeExpandedKeys': { type: Function as PropType<(keys: Key[]) => void> },
6773
'onUpdate:searchValue': { type: Function as PropType<(value: string) => void> },
@@ -107,6 +113,8 @@ const TreeSelect = defineComponent({
107113
});
108114

109115
const formItemContext = useInjectFormItemContext();
116+
const formItemInputContext = FormItemInputContext.useInject();
117+
const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
110118
const {
111119
prefixCls,
112120
renderEmpty,
@@ -118,8 +126,21 @@ const TreeSelect = defineComponent({
118126
getPrefixCls,
119127
} = useConfigInject('select', props);
120128
const rootPrefixCls = computed(() => getPrefixCls());
129+
// ===================== Placement =====================
130+
const placement = computed(() => {
131+
if (props.placement !== undefined) {
132+
return props.placement;
133+
}
134+
return direction.value === 'rtl'
135+
? ('bottomRight' as SelectCommonPlacement)
136+
: ('bottomLeft' as SelectCommonPlacement);
137+
});
121138
const transitionName = computed(() =>
122-
getTransitionName(rootPrefixCls.value, 'slide-up', props.transitionName),
139+
getTransitionName(
140+
rootPrefixCls.value,
141+
getTransitionDirection(placement.value),
142+
props.transitionName,
143+
),
123144
);
124145
const choiceTransitionName = computed(() =>
125146
getTransitionName(rootPrefixCls.value, '', props.choiceTransitionName),
@@ -134,6 +155,9 @@ const TreeSelect = defineComponent({
134155
);
135156

136157
const isMultiple = computed(() => !!(props.treeCheckable || props.multiple));
158+
const mergedShowArrow = computed(() =>
159+
props.showArrow !== undefined ? props.showArrow : props.loading || !isMultiple.value,
160+
);
137161

138162
const treeSelectRef = ref();
139163
expose({
@@ -173,15 +197,20 @@ const TreeSelect = defineComponent({
173197
multiple,
174198
treeIcon,
175199
treeLine,
200+
showArrow,
176201
switcherIcon = slots.switcherIcon?.(),
177202
fieldNames = props.replaceFields,
178203
id = formItemContext.id.value,
179204
} = props;
205+
const { isFormItemInput, hasFeedback, feedbackIcon } = formItemInputContext;
180206
// ===================== Icons =====================
181207
const { suffixIcon, removeIcon, clearIcon } = getIcons(
182208
{
183209
...props,
184210
multiple: isMultiple.value,
211+
showArrow: mergedShowArrow.value,
212+
hasFeedback,
213+
feedbackIcon,
185214
prefixCls: prefixCls.value,
186215
},
187216
slots,
@@ -202,6 +231,7 @@ const TreeSelect = defineComponent({
202231
'clearIcon',
203232
'switcherIcon',
204233
'bordered',
234+
'status',
205235
'onUpdate:value',
206236
'onUpdate:treeExpandedKeys',
207237
'onUpdate:searchValue',
@@ -214,7 +244,9 @@ const TreeSelect = defineComponent({
214244
[`${prefixCls.value}-sm`]: size.value === 'small',
215245
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
216246
[`${prefixCls.value}-borderless`]: !bordered,
247+
[`${prefixCls.value}-in-form-item`]: isFormItemInput,
217248
},
249+
getStatusClassNames(prefixCls.value, mergedStatus.value, hasFeedback),
218250
attrs.class,
219251
);
220252
const otherProps: any = {};
@@ -263,6 +295,8 @@ const TreeSelect = defineComponent({
263295
treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
264296
}}
265297
maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder}
298+
placement={placement.value}
299+
showArrow={hasFeedback || showArrow}
266300
/>
267301
);
268302
};

components/tree-select/index.zh-CN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
3535
| multiple | 支持多选(当设置 treeCheckable 时自动变为 true) | boolean | false | | |
3636
| notFoundContent | 当下拉列表为空时显示的内容 | slot | `Not Found` | | |
3737
| placeholder | 选择框默认文字 | string\|slot | - | | |
38+
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | 3.3.0 |
3839
| replaceFields | 替换 treeNode 中 title,value,key,children 字段为 treeData 中对应的字段 | object | {children:'children', label:'title', key:'key', value: 'value' } | | 1.6.1 (3.0.0 废弃) |
3940
| searchPlaceholder | 搜索框默认文字 | string\|slot | - | | |
4041
| searchValue(v-model) | 搜索框的值,可以通过 `search` 事件获取用户输入 | string | - | | |
4142
| showCheckedStrategy | 定义选中项回填的方式。`TreeSelect.SHOW_ALL`: 显示所有选中节点(包括父节点). `TreeSelect.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时). 默认只显示子节点. | enum{TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD | | |
4243
| showSearch | 在下拉中显示搜索框(仅在单选模式下生效) | boolean | false | | |
4344
| size | 选择框大小,可选 `large` `small` | string | 'default' | | |
45+
| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 |
4446
| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - | | |
4547
| tagRender | 自定义 tag 内容,多选时生效 | slot | - | 3.0 | |
4648
| title | 自定义标题 | slot | | 3.0.0 | |
@@ -53,6 +55,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
5355
| treeExpandedKeys(v-model) | 设置展开的树节点 | string\[] \| number\[] | - | | |
5456
| treeIcon | 是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true,需要自行定义图标相关样式 | boolean | false | | |
5557
| treeLine | 是否展示线条样式,请参考 [Tree - showLine](/components/tree/#components-tree-demo-line) | boolean \| object | false | 3.0 | |
58+
| treeLoadedKeys | (受控)已经加载的节点,需要配合 `loadData` 使用 | string[] | [] | 3.3.0 |
5659
| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | 'value' | | |
5760
| treeNodeLabelProp | 作为显示的 prop 设置 | string | 'title' | | |
5861
| value(v-model) | 指定当前选中的条目 | string/string\[] | - | | |

components/tree-select/style/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import '../../style/index.less';
22
import './index.less';
33

44
// style dependencies
5-
// deps-lint-skip: tree
5+
// deps-lint-skip: tree, form
66
import '../../select/style';
77
import '../../empty/style';

components/vc-tree-select/OptionList.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ export default defineComponent({
181181
open,
182182
notFoundContent = slots.notFoundContent?.(),
183183
} = baseProps;
184-
const { listHeight, listItemHeight, virtual } = context;
184+
const { listHeight, listItemHeight, virtual, dropdownMatchSelectWidth, treeExpandAction } =
185+
context;
185186
const {
186187
checkable,
187188
treeDefaultExpandAll,
@@ -228,7 +229,7 @@ export default defineComponent({
228229
treeData={memoTreeData.value as TreeDataNode[]}
229230
height={listHeight}
230231
itemHeight={listItemHeight}
231-
virtual={virtual}
232+
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
232233
multiple={multiple}
233234
icon={treeIcon}
234235
showIcon={showTreeIcon}
@@ -251,6 +252,7 @@ export default defineComponent({
251252
onExpand={onInternalExpand}
252253
onLoad={onTreeLoad}
253254
filterTreeNode={filterTreeNode}
255+
expandAction={treeExpandAction}
254256
v-slots={{ ...slots, checkable: legacyContext.customSlots.treeCheckable }}
255257
/>
256258
</div>

components/vc-tree-select/TreeSelect.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { conductCheck } from '../vc-tree/utils/conductUtil';
3030
import { warning } from '../vc-util/warning';
3131
import { toReactive } from '../_util/toReactive';
3232
import useMaxLevel from '../vc-tree/useMaxLevel';
33+
import type { ExpandAction } from '../tree/DirectoryTree';
3334

3435
export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void;
3536

@@ -180,6 +181,7 @@ export function treeSelectProps<
180181
switcherIcon: PropTypes.any,
181182
treeMotion: PropTypes.any,
182183
children: Array as PropType<VueNode[]>,
184+
treeExpandAction: String as PropType<ExpandAction>,
183185

184186
showArrow: { type: Boolean, default: undefined },
185187
showSearch: { type: Boolean, default: undefined },
@@ -621,8 +623,10 @@ export default defineComponent({
621623
switcherIcon,
622624
treeMotion,
623625
customSlots,
626+
627+
dropdownMatchSelectWidth,
628+
treeExpandAction,
624629
} = toRefs(props);
625-
toRaw;
626630
useProvideLegacySelectContext(
627631
toReactive({
628632
checkable: mergedCheckable,
@@ -654,6 +658,8 @@ export default defineComponent({
654658
treeData: filteredTreeData,
655659
fieldNames: mergedFieldNames,
656660
onSelect: onOptionSelect,
661+
dropdownMatchSelectWidth,
662+
treeExpandAction,
657663
} as unknown as TreeSelectContextProps),
658664
);
659665
const selectRef = ref<BaseSelectRef>();

components/vc-tree-select/TreeSelectContext.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import type { InjectionKey } from 'vue';
22
import { provide, inject } from 'vue';
3+
import type { ExpandAction } from '../tree/DirectoryTree';
34
import type { DefaultOptionType, InternalFieldName, OnInternalSelect } from './TreeSelect';
45

56
export interface TreeSelectContextProps {
67
virtual?: boolean;
8+
dropdownMatchSelectWidth?: boolean | number;
79
listHeight: number;
810
listItemHeight: number;
911
treeData: DefaultOptionType[];
1012
fieldNames: InternalFieldName;
1113
onSelect: OnInternalSelect;
14+
treeExpandAction?: ExpandAction;
1215
}
1316

1417
const TreeSelectContextPropsKey: InjectionKey<TreeSelectContextProps> = Symbol(

0 commit comments

Comments
 (0)