refactor: cascader

refactor-cascader
tangjinzhou 2022-01-21 15:01:11 +08:00
parent 616478be1f
commit 3a6cc6aa09
12 changed files with 182 additions and 74 deletions

View File

@ -77,7 +77,7 @@ const options: CascaderProps['options'] = [
];
export default defineComponent({
setup() {
const handleAreaClick = (e: Event, label: string, option: Option) => {
const handleAreaClick = (e: Event, label: string, option: CascaderProps['options'][number]) => {
e.stopPropagation();
console.log('clicked', label, option);
};

View File

@ -71,7 +71,7 @@ export default defineComponent({
const value = ref<string[]>([]);
const text = ref<string>('Unselect');
const onChange = (_value: string, selectedOptions: Option[]) => {
const onChange: CascaderProps['onChange'] = (_value, selectedOptions) => {
text.value = selectedOptions.map(o => o.label).join(', ');
};

View File

@ -11,6 +11,7 @@
<lazy />
<fields-name />
<suffix />
<multipleVue />
</demo-sort>
</template>
<script>
@ -25,6 +26,7 @@ import Search from './search.vue';
import Size from './size.vue';
import FieldsName from './fields-name.vue';
import Suffix from './suffix.vue';
import multipleVue from './multiple.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@ -44,6 +46,7 @@ export default defineComponent({
Size,
FieldsName,
Suffix,
multipleVue,
},
});
</script>

View File

@ -45,7 +45,7 @@ export default defineComponent({
},
]);
const loadData = (selectedOptions: Option[]) => {
const loadData: CascaderProps['loadData'] = selectedOptions => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;

View File

@ -0,0 +1,72 @@
<docs>
---
order: 5.1
version: 3.0.0
title:
zh-CN: 多选
en-US: Multiple
---
## zh-CN
一次性选择多个选项
## en-US
Select multiple options
</docs>
<template>
<a-cascader
v-model:value="value"
style="width: 233px"
multiple
max-tag-count="responsive"
:options="options"
placeholder="Please select"
></a-cascader>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import type { CascaderProps } from 'ant-design-vue';
const options: CascaderProps['options'] = [
{
label: 'Light',
value: 'light',
children: new Array(20)
.fill(null)
.map((_, index) => ({ label: `Number ${index}`, value: index })),
},
{
label: 'Bamboo',
value: 'bamboo',
children: [
{
label: 'Little',
value: 'little',
children: [
{
label: 'Toy Fish',
value: 'fish',
},
{
label: 'Toy Cards',
value: 'cards',
},
{
label: 'Toy Bird',
value: 'bird',
},
],
},
],
},
];
export default defineComponent({
setup() {
return {
value: ref<string[]>([]),
options,
};
},
});
</script>

View File

@ -28,6 +28,7 @@ Search and select options directly.
<script lang="ts">
import { defineComponent, ref } from 'vue';
import type { CascaderProps } from 'ant-design-vue';
import type { ShowSearchType } from 'ant-design-vue/es/cascader';
const options: CascaderProps['options'] = [
{
value: 'zhejiang',
@ -69,7 +70,7 @@ const options: CascaderProps['options'] = [
];
export default defineComponent({
setup() {
const filter = (inputValue: string, path: Option[]) => {
const filter: ShowSearchType['filter'] = (inputValue, path) => {
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
};

View File

@ -19,29 +19,34 @@ Cascade selection box.
<a-cascader :options="options" v-model:value="value" />
```
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| allowClear | whether allow clear | boolean | true |
| autofocus | get focus when component mounted | boolean | false |
| changeOnSelect | change value on each selection if set to true, see above demo for details | boolean | false |
| defaultValue | initial selected value | string\[] \| number\[] | \[] |
| disabled | whether disabled select | boolean | false |
| displayRender | render function of displaying selected options, you can use #displayRender="{labels, selectedOptions}" | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` |
| expandTrigger | expand current item when click or hover, one of 'click' 'hover' | string | 'click' |
| fieldNames | custom field name for label and value and children | object | `{ label: 'label', value: 'value', children: 'children' }` |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. | Function(triggerNode) | () => document.body |
| loadData | To load option lazily, and it cannot work with `showSearch` | `(selectedOptions) => void` | - |
| notFoundContent | Specify content to show when no result matches. | string | 'Not Found' |
| options | data options of cascade | [Option](#option)[] | - |
| placeholder | input placeholder | string | 'Please select' |
| popupClassName | additional className of popup overlay | string | - |
| popupStyle | additional style of popup overlay | object | {} |
| popupPlacement | use preset popup align config from builtinPlacements`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` |
| popupVisible | set visible of cascader popup | boolean | - |
| showSearch | Whether show search input in single mode. | boolean \| [object](#showsearch) | false |
| size | input size, one of `large` `default` `small` | string | `default` |
| suffixIcon | The custom suffix icon | string \| VNode \| slot | - |
| value(v-model) | selected value | string\[] \| number\[] | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| allowClear | whether allow clear | boolean | true | |
| autofocus | get focus when component mounted | boolean | false | |
| changeOnSelect | change value on each selection if set to true, see above demo for details | boolean | false | |
| disabled | whether disabled select | boolean | false | |
| displayRender | render function of displaying selected options, you can use #displayRender="{labels, selectedOptions}" | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | |
| expandTrigger | expand current item when click or hover, one of 'click' 'hover' | string | 'click' | |
| fieldNames | custom field name for label and value and children | object | `{ label: 'label', value: 'value', children: 'children' }` | |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. | Function(triggerNode) | () => document.body | |
| loadData | To load option lazily, and it cannot work with `showSearch` | `(selectedOptions) => void` | - | |
| notFoundContent | Specify content to show when no result matches. | string \| slot | 'Not Found' | |
| options | data options of cascade | [Option](#option)[] | - | |
| placeholder | input placeholder | string | 'Please select' | |
| showSearch | Whether show search input in single mode. | boolean \| [object](#showsearch) | false | |
| size | input size, one of `large` `default` `small` | string | `default` | |
| suffixIcon | The custom suffix icon | string \| VNode \| slot | - | |
| value(v-model) | selected value | string\[] \| number\[] | - | |
| expandIcon | Customize the current item expand icon | slot | - | 3.0 |
| maxTagCount | Max tag count to show. `responsive` will cost render performance | number \| `responsive` | - | 3.0 |
| maxTagPlaceholder | Placeholder for not showing tags | v-slot \| function(omittedValues) | - | 3.0 |
| dropdownClassName | additional className of popup overlay | string | - | 3.0 |
| dropdownStyle | additional style of popup overlay | CSSProperties | {} | 3.0 |
| open | set visible of cascader popup | boolean | - | 3.0 |
| placement | use preset popup align config from builtinPlacements`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | 3.0 |
| tagRender | Customize tag render when `multiple` | slot | - | 3.0 |
| multiple | Support multiple or not | boolean | - | 3.0 |
| searchValue | Set search valueNeed work with `showSearch` | string | - | 3.0 |
### showSearch
@ -60,7 +65,7 @@ Fields in `showSearch`:
| Events Name | Description | Arguments | version |
| --- | --- | --- | --- | --- |
| change | callback when finishing cascader select | `(value, selectedOptions) => void` | - | |
| popupVisibleChange | callback when popup shown or hidden | `(value) => void` | - | |
| dropdownVisibleChange | callback when popup shown or hidden | `(value) => void` | - | 3.0 |
| search | callback when input value change | `(value) => void` | - | 1.5.4 |
### Option

View File

@ -24,7 +24,7 @@ import type { ValueType } from '../vc-cascader/Cascader';
// - Hover opacity style
// - Search filter match case
export { BaseOptionType, DefaultOptionType };
export type { BaseOptionType, DefaultOptionType, ShowSearchType };
export type FieldNamesType = FieldNames;
@ -134,11 +134,6 @@ const Cascader = defineComponent({
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
watchEffect(() => {
devWarning(
props.popupClassName === undefined,
'Cascader',
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
);
devWarning(
!props.multiple || !props.displayRender || !slots.displayRender,
'Cascader',
@ -269,6 +264,7 @@ const Cascader = defineComponent({
checkable: () => <span class={`${cascaderPrefixCls.value}-checkbox-inner`} />,
}}
displayRender={props.displayRender || slots.displayRender}
maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder}
onChange={handleChange}
onBlur={handleBlur}
v-slots={slots}

View File

@ -20,29 +20,35 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
<a-cascader :options="options" v-model:value="value" />
```
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| allowClear | 是否支持清除 | boolean | true |
| autofocus | 自动获取焦点 | boolean | false |
| changeOnSelect | 当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false |
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] |
| disabled | 禁用 | boolean | false |
| displayRender | 选择后展示的渲染函数,可使用 #displayRender="{labels, selectedOptions}" | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` |
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | 'click' |
| fieldNames | 自定义 options 中 label name children 的字段 | object | `{ label: 'label', value: 'value', children: 'children' }` |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body |
| loadData | 用于动态加载选项,无法与 `showSearch` 一起使用 | `(selectedOptions) => void` | - |
| notFoundContent | 当下拉列表为空时显示的内容 | string | 'Not Found' |
| options | 可选项数据源 | [Option](#option)[] | - |
| placeholder | 输入框占位文本 | string | '请选择' |
| popupClassName | 自定义浮层类名 | string | - |
| popupStyle | 自定义浮层样式 | object | {} |
| popupPlacement | 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` | Enum | `bottomLeft` |
| popupVisible | 控制浮层显隐 | boolean | - |
| showSearch | 在选择框中显示搜索框 | boolean \| [object](#showsearch) | false |
| size | 输入框大小,可选 `large` `default` `small` | string | `default` |
| suffixIcon | 自定义的选择框后缀图标 | string \| VNode \| slot | - |
| value(v-model) | 指定选中项 | string\[] \| number\[] | - |
| 参数 | 说明 | 类型 | 默认值 | Version |
| --- | --- | --- | --- | --- |
| allowClear | 是否支持清除 | boolean | true | |
| autofocus | 自动获取焦点 | boolean | false | |
| changeOnSelect | 当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | |
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
| disabled | 禁用 | boolean | false | |
| displayRender | 选择后展示的渲染函数,可使用 #displayRender="{labels, selectedOptions}" | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | |
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | 'click' | |
| fieldNames | 自定义 options 中 label name children 的字段 | object | `{ label: 'label', value: 'value', children: 'children' }` | |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body | |
| loadData | 用于动态加载选项,无法与 `showSearch` 一起使用 | `(selectedOptions) => void` | - | |
| notFoundContent | 当下拉列表为空时显示的内容 | string \| slot | 'Not Found' | |
| options | 可选项数据源 | [Option](#option)[] | - | |
| placeholder | 输入框占位文本 | string | '请选择' | |
| showSearch | 在选择框中显示搜索框 | boolean \| [object](#showsearch) | false | |
| size | 输入框大小,可选 `large` `default` `small` | string | `default` | |
| suffixIcon | 自定义的选择框后缀图标 | string \| VNode \| slot | - | |
| value(v-model) | 指定选中项 | string\[] \| number\[] | - | |
| expandIcon | 自定义次级菜单展开图标 | slot | - | 3.0 |
| maxTagCount | 最多显示多少个 tag响应式模式会对性能产生损耗 | number \| `responsive` | - | 3.0 |
| maxTagPlaceholder | 隐藏 tag 时显示的内容 | v-slot \| function(omittedValues) | - | 3.0 |
| dropdownClassName | 自定义浮层类名 | string | - | 3.0 |
| dropdownStyle | 自定义浮层样式 | CSSProperties | {} | 3.0 |
| open | 控制浮层显隐 | boolean | - | 3.0 |
| placement | 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | 3.0 |
| tagRender | 自定义 tag 内容,多选时生效 | (props) => ReactNode | - | 3.0 |
| multiple | 支持多选节点 | boolean | - | 3.0 |
| searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 3.0 |
### showSearch
@ -59,17 +65,17 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
### 事件
| 事件名称 | 说明 | 回调参数 | 版本 |
| ------------------ | ------------------- | ---------------------------------- | ---- | ----- |
| --------------------- | ---------------------- | ---------------------------------- | ---- | --- |
| change | 选择完成后的回调 | `(value, selectedOptions) => void` | - | |
| popupVisibleChange | 显示/隐藏浮层的回调 | `(value) => void` | - | |
| search | 输入框变化时的回调 | `(value) => void` | - | 1.5.4 |
| dropdownVisibleChange | 显示/隐藏浮层的回调 | `(value) => void` | - | 3.0 |
| search | 监听搜索,返回输入的值 | `(value) => void` | - | 3.0 |
### Option
```ts
interface Option {
value: string | number;
label?: VNode;
label?: any;
disabled?: boolean;
children?: Option[];
}

View File

@ -17,10 +17,10 @@ import useMissingValues from './hooks/useMissingValues';
import { formatStrategyValues, toPathOptions } from './utils/treeUtil';
import { conductCheck } from '../vc-tree/utils/conductUtil';
import useDisplayValues from './hooks/useDisplayValues';
import { warning } from '../vc-util/warning';
import { useProvideCascader } from './context';
import OptionList from './OptionList';
import { BaseSelect } from '../vc-select';
import devWarning from '../vc-util/devWarning';
export interface ShowSearchType<OptionType extends BaseOptionType = DefaultOptionType> {
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean;
@ -81,7 +81,10 @@ function baseCascaderProps<OptionType extends BaseOptionType = DefaultOptionType
checkable: { type: Boolean, default: undefined },
// Search
showSearch: { type: [Boolean, Object] as PropType<boolean | ShowSearchType<OptionType>> },
showSearch: {
type: [Boolean, Object] as PropType<boolean | ShowSearchType<OptionType>>,
default: undefined as boolean | ShowSearchType<OptionType>,
},
searchValue: String,
onSearch: Function as PropType<(value: string) => void>,
@ -101,7 +104,14 @@ function baseCascaderProps<OptionType extends BaseOptionType = DefaultOptionType
/** @deprecated Use `dropdownClassName` instead */
popupClassName: String,
dropdownClassName: String,
dropdownMenuColumnStyle: Object as PropType<CSSProperties>,
dropdownMenuColumnStyle: {
type: Object as PropType<CSSProperties>,
default: undefined as CSSProperties,
},
/** @deprecated Use `dropdownStyle` instead */
popupStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
dropdownStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
/** @deprecated Use `placement` instead */
popupPlacement: String as PropType<Placement>,
@ -266,7 +276,11 @@ export default defineComponent({
const { checkedKeys, halfCheckedKeys } = conductCheck(keyPathValues, true, ketPathEntities);
// Convert key back to value cells
return [getValueByKeyPath(checkedKeys), getValueByKeyPath(halfCheckedKeys), missingValues];
[checkedValues.value, halfCheckedValues.value, missingCheckedValues.value] = [
getValueByKeyPath(checkedKeys),
getValueByKeyPath(halfCheckedKeys),
missingValues,
];
});
const deDuplicatedValues = computed(() => {
@ -371,22 +385,31 @@ export default defineComponent({
// ============================ Open ============================
if (process.env.NODE_ENV !== 'production') {
watchEffect(() => {
warning(
devWarning(
!props.onPopupVisibleChange,
'Cascader',
'`popupVisibleChange` is deprecated. Please use `dropdownVisibleChange` instead.',
);
warning(
devWarning(
props.popupVisible === undefined,
'Cascader',
'`popupVisible` is deprecated. Please use `open` instead.',
);
warning(
devWarning(
props.popupClassName === undefined,
'Cascader',
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
);
warning(
devWarning(
props.popupPlacement === undefined,
'Cascader',
'`popupPlacement` is deprecated. Please use `placement` instead.',
);
devWarning(
props.popupStyle === undefined,
'Cascader',
'`popupStyle` is deprecated. Please use `dropdownStyle` instead.',
);
});
}
@ -394,6 +417,8 @@ export default defineComponent({
const mergedDropdownClassName = computed(() => props.dropdownClassName || props.popupClassName);
const mergedDropdownStyle = computed(() => props.dropdownStyle || props.popupStyle || {});
const mergedPlacement = computed(() => props.placement || props.popupPlacement);
const onInternalDropdownVisibleChange = (nextVisible: boolean) => {
@ -514,7 +539,7 @@ export default defineComponent({
id={mergedId}
prefixCls={props.prefixCls}
dropdownMatchSelectWidth={false}
dropdownStyle={dropdownStyle}
dropdownStyle={{ ...mergedDropdownStyle.value, ...dropdownStyle }}
// Value
displayValues={displayValues.value}
onDisplayValuesChange={onDisplayValuesChange}

View File

@ -18,7 +18,7 @@ export default function Checkbox({
}: CheckboxProps) {
const { customSlots, checkable } = useInjectCascader();
const mergedCheckable = checkable.value === undefined ? customSlots.value.checkable : checkable;
const mergedCheckable = checkable.value !== false ? customSlots.value.checkable : checkable.value;
const customCheckbox =
typeof mergedCheckable === 'function'
? mergedCheckable()

View File

@ -307,7 +307,7 @@ const Overflow = defineComponent({
let restNode = () => null;
const restContextProps = {
order: displayRest ? mergedDisplayCount.value : Number.MAX_SAFE_INTEGER,
className: `${itemPrefixCls.value}-rest`,
className: `${itemPrefixCls.value} ${itemPrefixCls.value}-rest`,
registerSize: registerOverflowSize,
display: displayRest,
};