feat: cascader add clearIcon & removeIcon slot
parent
f1f6085dbb
commit
6d2bcf0ab8
|
@ -13,6 +13,17 @@ import {
|
||||||
Transition as T,
|
Transition as T,
|
||||||
TransitionGroup as TG,
|
TransitionGroup as TG,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
import { tuple } from './type';
|
||||||
|
|
||||||
|
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
|
||||||
|
export type SelectCommonPlacement = typeof SelectPlacements[number];
|
||||||
|
|
||||||
|
const getTransitionDirection = (placement: SelectCommonPlacement | undefined) => {
|
||||||
|
if (placement !== undefined && (placement === 'topLeft' || placement === 'topRight')) {
|
||||||
|
return `slide-down`;
|
||||||
|
}
|
||||||
|
return `slide-up`;
|
||||||
|
};
|
||||||
|
|
||||||
export const getTransitionProps = (transitionName: string, opt: TransitionProps = {}) => {
|
export const getTransitionProps = (transitionName: string, opt: TransitionProps = {}) => {
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
@ -176,6 +187,6 @@ const getTransitionName = (rootPrefixCls: string, motion: string, transitionName
|
||||||
return `${rootPrefixCls}-${motion}`;
|
return `${rootPrefixCls}-${motion}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Transition, TransitionGroup, collapseMotion, getTransitionName };
|
export { Transition, TransitionGroup, collapseMotion, getTransitionName, getTransitionDirection };
|
||||||
|
|
||||||
export default Transition;
|
export default Transition;
|
||||||
|
|
|
@ -16,21 +16,23 @@ Custom suffix icon
|
||||||
|
|
||||||
</docs>
|
</docs>
|
||||||
<template>
|
<template>
|
||||||
<a-cascader
|
<a-space>
|
||||||
v-model:value="value1"
|
<a-cascader
|
||||||
style="margin-top: 1rem"
|
v-model:value="value1"
|
||||||
:options="options"
|
style="margin-top: 1rem"
|
||||||
placeholder="Please select"
|
:options="options"
|
||||||
>
|
placeholder="Please select"
|
||||||
<template #suffixIcon><smile-outlined class="test" /></template>
|
>
|
||||||
</a-cascader>
|
<template #suffixIcon><smile-outlined class="test" /></template>
|
||||||
<a-cascader
|
</a-cascader>
|
||||||
v-model:value="value2"
|
<a-cascader
|
||||||
suffix-icon="ab"
|
v-model:value="value2"
|
||||||
style="margin-top: 1rem"
|
suffix-icon="ab"
|
||||||
:options="options"
|
style="margin-top: 1rem"
|
||||||
placeholder="Please select"
|
:options="options"
|
||||||
/>
|
placeholder="Please select"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SmileOutlined } from '@ant-design/icons-vue';
|
import { SmileOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
|
@ -23,6 +23,8 @@ Cascade selection box.
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| allowClear | whether allow clear | boolean | true | |
|
| allowClear | whether allow clear | boolean | true | |
|
||||||
| autofocus | get focus when component mounted | boolean | false | |
|
| autofocus | get focus when component mounted | boolean | false | |
|
||||||
|
| bordered | Whether has border style | boolean | true | 3.2 |
|
||||||
|
| clearIcon | The custom clear icon | slot | - | 3.2 |
|
||||||
| changeOnSelect | (Work on single select) change value on each selection if set to true, see above demo for details | boolean | false | |
|
| changeOnSelect | (Work on single select) change value on each selection if set to true, see above demo for details | boolean | false | |
|
||||||
| disabled | whether disabled select | 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(' / ')` | |
|
| displayRender | render function of displaying selected options, you can use #displayRender="{labels, selectedOptions}". | `({labels, selectedOptions}) => VNode` | `labels => labels.join(' / ')` | |
|
||||||
|
@ -41,6 +43,7 @@ Cascade selection box.
|
||||||
| options | data options of cascade | [Option](#option)\[] | - | |
|
| options | data options of cascade | [Option](#option)\[] | - | |
|
||||||
| placeholder | input placeholder | string | 'Please select' | |
|
| placeholder | input placeholder | string | 'Please select' | |
|
||||||
| placement | Use preset popup align config from builtinPlacements | `bottomLeft` \| `bottomRight` \| `topLeft` \| `topRight` | `bottomLeft` | 3.0 |
|
| placement | Use preset popup align config from builtinPlacements | `bottomLeft` \| `bottomRight` \| `topLeft` \| `topRight` | `bottomLeft` | 3.0 |
|
||||||
|
| removeIcon | The custom remove icon | slot | - | 3.2 |
|
||||||
| searchValue | Set search value,Need work with `showSearch` | string | - | 3.0 |
|
| searchValue | Set search value,Need work with `showSearch` | string | - | 3.0 |
|
||||||
| showSearch | Whether show search input in single mode. | boolean \| [object](#showsearch) | false | |
|
| showSearch | Whether show search input in single mode. | boolean \| [object](#showsearch) | false | |
|
||||||
| size | input size | `large` \| `default` \| `small` | `default` | |
|
| size | input size | `large` \| `default` \| `small` | `default` | |
|
||||||
|
|
|
@ -15,7 +15,8 @@ import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import type { SizeType } from '../config-provider';
|
import type { SizeType } from '../config-provider';
|
||||||
import devWarning from '../vc-util/devWarning';
|
import devWarning from '../vc-util/devWarning';
|
||||||
import { getTransitionName } from '../_util/transition';
|
import type { SelectCommonPlacement } from '../_util/transition';
|
||||||
|
import { getTransitionDirection, getTransitionName } from '../_util/transition';
|
||||||
import { useInjectFormItemContext } from '../form';
|
import { useInjectFormItemContext } from '../form';
|
||||||
import type { ValueType } from '../vc-cascader/Cascader';
|
import type { ValueType } from '../vc-cascader/Cascader';
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ export function cascaderProps<DataNodeType extends CascaderOptionType = Cascader
|
||||||
multiple: { type: Boolean, default: undefined },
|
multiple: { type: Boolean, default: undefined },
|
||||||
size: String as PropType<SizeType>,
|
size: String as PropType<SizeType>,
|
||||||
bordered: { type: Boolean, default: undefined },
|
bordered: { type: Boolean, default: undefined },
|
||||||
|
placement: { type: String as PropType<SelectCommonPlacement> },
|
||||||
suffixIcon: PropTypes.any,
|
suffixIcon: PropTypes.any,
|
||||||
options: Array as PropType<DataNodeType[]>,
|
options: Array as PropType<DataNodeType[]>,
|
||||||
'onUpdate:value': Function as PropType<(value: ValueType) => void>,
|
'onUpdate:value': Function as PropType<(value: ValueType) => void>,
|
||||||
|
@ -191,7 +192,17 @@ const Cascader = defineComponent({
|
||||||
emit('blur', ...args);
|
emit('blur', ...args);
|
||||||
formItemContext.onFieldBlur();
|
formItemContext.onFieldBlur();
|
||||||
};
|
};
|
||||||
|
const mergedShowArrow = computed(() =>
|
||||||
|
props.showArrow !== undefined ? props.showArrow : props.loading || !props.multiple,
|
||||||
|
);
|
||||||
|
const placement = computed(() => {
|
||||||
|
if (props.placement !== undefined) {
|
||||||
|
return props.placement;
|
||||||
|
}
|
||||||
|
return direction.value === 'rtl'
|
||||||
|
? ('bottomRight' as SelectCommonPlacement)
|
||||||
|
: ('bottomLeft' as SelectCommonPlacement);
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
notFoundContent = slots.notFoundContent?.(),
|
notFoundContent = slots.notFoundContent?.(),
|
||||||
|
@ -225,6 +236,7 @@ const Cascader = defineComponent({
|
||||||
...props,
|
...props,
|
||||||
multiple,
|
multiple,
|
||||||
prefixCls: prefixCls.value,
|
prefixCls: prefixCls.value,
|
||||||
|
showArrow: mergedShowArrow.value,
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
|
@ -245,6 +257,7 @@ const Cascader = defineComponent({
|
||||||
attrs.class,
|
attrs.class,
|
||||||
]}
|
]}
|
||||||
direction={direction.value}
|
direction={direction.value}
|
||||||
|
placement={placement.value}
|
||||||
notFoundContent={mergedNotFoundContent}
|
notFoundContent={mergedNotFoundContent}
|
||||||
allowClear={allowClear}
|
allowClear={allowClear}
|
||||||
showSearch={mergedShowSearch.value}
|
showSearch={mergedShowSearch.value}
|
||||||
|
@ -257,7 +270,11 @@ const Cascader = defineComponent({
|
||||||
dropdownClassName={mergedDropdownClassName.value}
|
dropdownClassName={mergedDropdownClassName.value}
|
||||||
dropdownPrefixCls={cascaderPrefixCls.value}
|
dropdownPrefixCls={cascaderPrefixCls.value}
|
||||||
choiceTransitionName={getTransitionName(rootPrefixCls.value, '', choiceTransitionName)}
|
choiceTransitionName={getTransitionName(rootPrefixCls.value, '', choiceTransitionName)}
|
||||||
transitionName={getTransitionName(rootPrefixCls.value, 'slide-up', transitionName)}
|
transitionName={getTransitionName(
|
||||||
|
rootPrefixCls.value,
|
||||||
|
getTransitionDirection(placement.value),
|
||||||
|
transitionName,
|
||||||
|
)}
|
||||||
getPopupContainer={getPopupContainer.value}
|
getPopupContainer={getPopupContainer.value}
|
||||||
customSlots={{
|
customSlots={{
|
||||||
...slots,
|
...slots,
|
||||||
|
@ -265,6 +282,7 @@ const Cascader = defineComponent({
|
||||||
}}
|
}}
|
||||||
displayRender={props.displayRender || slots.displayRender}
|
displayRender={props.displayRender || slots.displayRender}
|
||||||
maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder}
|
maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder}
|
||||||
|
showArrow={props.showArrow}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
v-slots={slots}
|
v-slots={slots}
|
||||||
|
|
|
@ -24,6 +24,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| allowClear | 是否支持清除 | boolean | true | |
|
| allowClear | 是否支持清除 | boolean | true | |
|
||||||
| autofocus | 自动获取焦点 | boolean | false | |
|
| autofocus | 自动获取焦点 | boolean | false | |
|
||||||
|
| bordered | 是否有边框 | boolean | true | 3.2 |
|
||||||
|
| clearIcon | 自定义的选择框清空图标 | slot | - | 3.2 |
|
||||||
| changeOnSelect | (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | |
|
| changeOnSelect | (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | |
|
||||||
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
|
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
|
||||||
| disabled | 禁用 | boolean | false | |
|
| disabled | 禁用 | boolean | false | |
|
||||||
|
@ -43,6 +45,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
|
||||||
| options | 可选项数据源 | [Option](#option)\[] | - | |
|
| options | 可选项数据源 | [Option](#option)\[] | - | |
|
||||||
| placeholder | 输入框占位文本 | string | '请选择' | |
|
| placeholder | 输入框占位文本 | string | '请选择' | |
|
||||||
| placement | 浮层预设位置 | `bottomLeft` \| `bottomRight` \| `topLeft` \| `topRight` | `bottomLeft` | 3.0 |
|
| placement | 浮层预设位置 | `bottomLeft` \| `bottomRight` \| `topLeft` \| `topRight` | `bottomLeft` | 3.0 |
|
||||||
|
| removeIcon | 自定义的多选框清除图标 | slot | - | 3.2 |
|
||||||
| searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 3.0 |
|
| searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 3.0 |
|
||||||
| showSearch | 在选择框中显示搜索框 | boolean \| [object](#showsearch) | false | |
|
| showSearch | 在选择框中显示搜索框 | boolean \| [object](#showsearch) | false | |
|
||||||
| size | 输入框大小 | `large` \| `default` \| `small` | `default` | |
|
| size | 输入框大小 | `large` \| `default` \| `small` | `default` | |
|
||||||
|
|
|
@ -9,7 +9,7 @@ import PropTypes from '../_util/vue-types';
|
||||||
import { initDefaultProps } from '../_util/props-util';
|
import { initDefaultProps } from '../_util/props-util';
|
||||||
import useId from '../vc-select/hooks/useId';
|
import useId from '../vc-select/hooks/useId';
|
||||||
import useMergedState from '../_util/hooks/useMergedState';
|
import useMergedState from '../_util/hooks/useMergedState';
|
||||||
import { fillFieldNames, toPathKey, toPathKeys } from './utils/commonUtil';
|
import { fillFieldNames, toPathKey, toPathKeys, SHOW_PARENT, SHOW_CHILD } from './utils/commonUtil';
|
||||||
import useEntities from './hooks/useEntities';
|
import useEntities from './hooks/useEntities';
|
||||||
import useSearchConfig from './hooks/useSearchConfig';
|
import useSearchConfig from './hooks/useSearchConfig';
|
||||||
import useSearchOptions from './hooks/useSearchOptions';
|
import useSearchOptions from './hooks/useSearchOptions';
|
||||||
|
@ -23,6 +23,7 @@ import { BaseSelect } from '../vc-select';
|
||||||
import devWarning from '../vc-util/devWarning';
|
import devWarning from '../vc-util/devWarning';
|
||||||
import useMaxLevel from '../vc-tree/useMaxLevel';
|
import useMaxLevel from '../vc-tree/useMaxLevel';
|
||||||
|
|
||||||
|
export { SHOW_PARENT, SHOW_CHILD };
|
||||||
export interface ShowSearchType<OptionType extends BaseOptionType = DefaultOptionType> {
|
export interface ShowSearchType<OptionType extends BaseOptionType = DefaultOptionType> {
|
||||||
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean;
|
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean;
|
||||||
render?: (arg?: {
|
render?: (arg?: {
|
||||||
|
@ -49,6 +50,7 @@ export interface InternalFieldNames extends Required<FieldNames> {
|
||||||
export type SingleValueType = (string | number)[];
|
export type SingleValueType = (string | number)[];
|
||||||
|
|
||||||
export type ValueType = SingleValueType | SingleValueType[];
|
export type ValueType = SingleValueType | SingleValueType[];
|
||||||
|
export type ShowCheckedStrategy = typeof SHOW_PARENT | typeof SHOW_CHILD;
|
||||||
|
|
||||||
export interface BaseOptionType {
|
export interface BaseOptionType {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -73,14 +75,11 @@ function baseCascaderProps<OptionType extends BaseOptionType = DefaultOptionType
|
||||||
value: { type: [String, Number, Array] as PropType<ValueType> },
|
value: { type: [String, Number, Array] as PropType<ValueType> },
|
||||||
defaultValue: { type: [String, Number, Array] as PropType<ValueType> },
|
defaultValue: { type: [String, Number, Array] as PropType<ValueType> },
|
||||||
changeOnSelect: { type: Boolean, default: undefined },
|
changeOnSelect: { type: Boolean, default: undefined },
|
||||||
onChange: Function as PropType<
|
|
||||||
(value: ValueType, selectedOptions?: OptionType[] | OptionType[][]) => void
|
|
||||||
>,
|
|
||||||
displayRender: Function as PropType<
|
displayRender: Function as PropType<
|
||||||
(opt: { labels: string[]; selectedOptions?: OptionType[] }) => any
|
(opt: { labels: string[]; selectedOptions?: OptionType[] }) => any
|
||||||
>,
|
>,
|
||||||
checkable: { type: Boolean, default: undefined },
|
checkable: { type: Boolean, default: undefined },
|
||||||
|
showCheckedStrategy: { type: String as PropType<ShowCheckedStrategy>, default: SHOW_PARENT },
|
||||||
// Search
|
// Search
|
||||||
showSearch: {
|
showSearch: {
|
||||||
type: [Boolean, Object] as PropType<boolean | ShowSearchType<OptionType>>,
|
type: [Boolean, Object] as PropType<boolean | ShowSearchType<OptionType>>,
|
||||||
|
@ -184,7 +183,7 @@ function toRawValues(value: ValueType): SingleValueType[] {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.length === 0 ? [] : [value];
|
return (value.length === 0 ? [] : [value]).map(val => (Array.isArray(val) ? val : [val]));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -215,10 +214,10 @@ export default defineComponent({
|
||||||
|
|
||||||
/** Convert path key back to value format */
|
/** Convert path key back to value format */
|
||||||
const getValueByKeyPath = (pathKeys: Key[]): SingleValueType[] => {
|
const getValueByKeyPath = (pathKeys: Key[]): SingleValueType[] => {
|
||||||
const ketPathEntities = pathKeyEntities.value;
|
const keyPathEntities = pathKeyEntities.value;
|
||||||
|
|
||||||
return pathKeys.map(pathKey => {
|
return pathKeys.map(pathKey => {
|
||||||
const { nodes } = ketPathEntities[pathKey];
|
const { nodes } = keyPathEntities[pathKey];
|
||||||
|
|
||||||
return nodes.map(node => node[mergedFieldNames.value.value]);
|
return nodes.map(node => node[mergedFieldNames.value.value]);
|
||||||
});
|
});
|
||||||
|
@ -275,12 +274,12 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyPathValues = toPathKeys(existValues);
|
const keyPathValues = toPathKeys(existValues);
|
||||||
const ketPathEntities = pathKeyEntities.value;
|
const keyPathEntities = pathKeyEntities.value;
|
||||||
|
|
||||||
const { checkedKeys, halfCheckedKeys } = conductCheck(
|
const { checkedKeys, halfCheckedKeys } = conductCheck(
|
||||||
keyPathValues,
|
keyPathValues,
|
||||||
true,
|
true,
|
||||||
ketPathEntities,
|
keyPathEntities,
|
||||||
maxLevel.value,
|
maxLevel.value,
|
||||||
levelEntities.value,
|
levelEntities.value,
|
||||||
);
|
);
|
||||||
|
@ -295,7 +294,11 @@ export default defineComponent({
|
||||||
|
|
||||||
const deDuplicatedValues = computed(() => {
|
const deDuplicatedValues = computed(() => {
|
||||||
const checkedKeys = toPathKeys(checkedValues.value);
|
const checkedKeys = toPathKeys(checkedValues.value);
|
||||||
const deduplicateKeys = formatStrategyValues(checkedKeys, pathKeyEntities.value);
|
const deduplicateKeys = formatStrategyValues(
|
||||||
|
checkedKeys,
|
||||||
|
pathKeyEntities.value,
|
||||||
|
props.showCheckedStrategy,
|
||||||
|
);
|
||||||
return [...missingCheckedValues.value, ...getValueByKeyPath(deduplicateKeys)];
|
return [...missingCheckedValues.value, ...getValueByKeyPath(deduplicateKeys)];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -330,6 +333,7 @@ export default defineComponent({
|
||||||
|
|
||||||
// =========================== Select ===========================
|
// =========================== Select ===========================
|
||||||
const onInternalSelect = (valuePath: SingleValueType) => {
|
const onInternalSelect = (valuePath: SingleValueType) => {
|
||||||
|
setSearchValue('');
|
||||||
if (!multiple.value) {
|
if (!multiple.value) {
|
||||||
triggerChange(valuePath);
|
triggerChange(valuePath);
|
||||||
} else {
|
} else {
|
||||||
|
@ -379,7 +383,11 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Roll up to parent level keys
|
// Roll up to parent level keys
|
||||||
const deDuplicatedKeys = formatStrategyValues(checkedKeys, pathKeyEntities.value);
|
const deDuplicatedKeys = formatStrategyValues(
|
||||||
|
checkedKeys,
|
||||||
|
pathKeyEntities.value,
|
||||||
|
props.showCheckedStrategy,
|
||||||
|
);
|
||||||
nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
|
nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +545,7 @@ export default defineComponent({
|
||||||
return () => {
|
return () => {
|
||||||
const emptyOptions = !(mergedSearchValue.value ? searchOptions.value : mergedOptions.value)
|
const emptyOptions = !(mergedSearchValue.value ? searchOptions.value : mergedOptions.value)
|
||||||
.length;
|
.length;
|
||||||
|
const { dropdownMatchSelectWidth = false } = props;
|
||||||
const dropdownStyle: CSSProperties =
|
const dropdownStyle: CSSProperties =
|
||||||
// Search to match width
|
// Search to match width
|
||||||
(mergedSearchValue.value && mergedSearchConfig.value.matchInputWidth) ||
|
(mergedSearchValue.value && mergedSearchConfig.value.matchInputWidth) ||
|
||||||
|
@ -555,7 +563,7 @@ export default defineComponent({
|
||||||
ref={selectRef}
|
ref={selectRef}
|
||||||
id={mergedId}
|
id={mergedId}
|
||||||
prefixCls={props.prefixCls}
|
prefixCls={props.prefixCls}
|
||||||
dropdownMatchSelectWidth={false}
|
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
|
||||||
dropdownStyle={{ ...mergedDropdownStyle.value, ...dropdownStyle }}
|
dropdownStyle={{ ...mergedDropdownStyle.value, ...dropdownStyle }}
|
||||||
// Value
|
// Value
|
||||||
displayValues={displayValues.value}
|
displayValues={displayValues.value}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { DefaultOptionType, SingleValueType } from '../Cascader';
|
||||||
import { SEARCH_MARK } from '../hooks/useSearchOptions';
|
import { SEARCH_MARK } from '../hooks/useSearchOptions';
|
||||||
import type { Key } from '../../_util/type';
|
import type { Key } from '../../_util/type';
|
||||||
import { useInjectCascader } from '../context';
|
import { useInjectCascader } from '../context';
|
||||||
|
export const FIX_LABEL = '__cascader_fix_label__';
|
||||||
export interface ColumnProps {
|
export interface ColumnProps {
|
||||||
prefixCls: string;
|
prefixCls: string;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
|
@ -58,7 +58,7 @@ export default function Column({
|
||||||
{options.map(option => {
|
{options.map(option => {
|
||||||
const { disabled } = option;
|
const { disabled } = option;
|
||||||
const searchOptions = option[SEARCH_MARK];
|
const searchOptions = option[SEARCH_MARK];
|
||||||
const label = option[fieldNames.value.label];
|
const label = option[FIX_LABEL] ?? option[fieldNames.value.label];
|
||||||
const value = option[fieldNames.value.value];
|
const value = option[fieldNames.value.value];
|
||||||
|
|
||||||
const isMergedLeaf = isLeaf(option, fieldNames.value);
|
const isMergedLeaf = isLeaf(option, fieldNames.value);
|
||||||
|
@ -132,6 +132,10 @@ export default function Column({
|
||||||
triggerOpenPath();
|
triggerOpenPath();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onMousedown={e => {
|
||||||
|
// Prevent selector from blurring
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{multiple && (
|
{multiple && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -145,7 +149,7 @@ export default function Column({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div class={`${menuItemPrefixCls}-content`}>{option[fieldNames.value.label]}</div>
|
<div class={`${menuItemPrefixCls}-content`}>{label}</div>
|
||||||
{!isLoading && expandIcon && !isMergedLeaf && (
|
{!isLoading && expandIcon && !isMergedLeaf && (
|
||||||
<div class={`${menuItemPrefixCls}-expand-icon`}>{expandIcon}</div>
|
<div class={`${menuItemPrefixCls}-expand-icon`}>{expandIcon}</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
/* eslint-disable default-case */
|
/* eslint-disable default-case */
|
||||||
import Column from './Column';
|
|
||||||
import type { DefaultOptionType, SingleValueType } from '../Cascader';
|
import type { DefaultOptionType, SingleValueType } from '../Cascader';
|
||||||
import { isLeaf, toPathKey, toPathKeys, toPathValueStr } from '../utils/commonUtil';
|
import {
|
||||||
|
isLeaf,
|
||||||
|
toPathKey,
|
||||||
|
toPathKeys,
|
||||||
|
toPathValueStr,
|
||||||
|
scrollIntoParentView,
|
||||||
|
} from '../utils/commonUtil';
|
||||||
import useActive from './useActive';
|
import useActive from './useActive';
|
||||||
import useKeyboard from './useKeyboard';
|
import useKeyboard from './useKeyboard';
|
||||||
import { toPathOptions } from '../utils/treeUtil';
|
import { toPathOptions } from '../utils/treeUtil';
|
||||||
import { computed, defineComponent, ref, shallowRef, watchEffect } from 'vue';
|
import { computed, defineComponent, onMounted, ref, shallowRef, watch, watchEffect } from 'vue';
|
||||||
import { useBaseProps } from '../../vc-select';
|
import { useBaseProps } from '../../vc-select';
|
||||||
import { useInjectCascader } from '../context';
|
import { useInjectCascader } from '../context';
|
||||||
import type { Key } from '../../_util/type';
|
import type { Key } from '../../_util/type';
|
||||||
import type { EventHandler } from '../../_util/EventInterface';
|
import type { EventHandler } from '../../_util/EventInterface';
|
||||||
|
import Column, { FIX_LABEL } from './Column';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'OptionList',
|
name: 'OptionList',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
@ -149,18 +154,29 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useKeyboard(
|
useKeyboard(context, mergedOptions, fieldNames, activeValueCells, onPathOpen, onKeyboardSelect);
|
||||||
context,
|
|
||||||
mergedOptions,
|
|
||||||
fieldNames,
|
|
||||||
activeValueCells,
|
|
||||||
onPathOpen,
|
|
||||||
containerRef,
|
|
||||||
onKeyboardSelect,
|
|
||||||
);
|
|
||||||
const onListMouseDown: EventHandler = event => {
|
const onListMouseDown: EventHandler = event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
watch(
|
||||||
|
activeValueCells,
|
||||||
|
cells => {
|
||||||
|
for (let i = 0; i < cells.length; i += 1) {
|
||||||
|
const cellPath = cells.slice(0, i + 1);
|
||||||
|
const cellKeyPath = toPathKey(cellPath);
|
||||||
|
const ele = containerRef.value?.querySelector<HTMLElement>(
|
||||||
|
`li[data-path-key="${cellKeyPath.replace(/\\{0,2}"/g, '\\"')}"]`, // matches unescaped double quotes
|
||||||
|
);
|
||||||
|
if (ele) {
|
||||||
|
scrollIntoParentView(ele);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'post', immediate: true },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// ========================== Render ==========================
|
// ========================== Render ==========================
|
||||||
const {
|
const {
|
||||||
|
@ -173,8 +189,8 @@ export default defineComponent({
|
||||||
|
|
||||||
const emptyList: DefaultOptionType[] = [
|
const emptyList: DefaultOptionType[] = [
|
||||||
{
|
{
|
||||||
[fieldNames.value.label as 'label']: notFoundContent,
|
|
||||||
[fieldNames.value.value as 'value']: '__EMPTY__',
|
[fieldNames.value.value as 'value']: '__EMPTY__',
|
||||||
|
[FIX_LABEL as 'label']: notFoundContent,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -3,9 +3,9 @@ import type { Key } from '../../_util/type';
|
||||||
import type { Ref, SetupContext } from 'vue';
|
import type { Ref, SetupContext } from 'vue';
|
||||||
import { computed, ref, watchEffect } from 'vue';
|
import { computed, ref, watchEffect } from 'vue';
|
||||||
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
|
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
|
||||||
import { toPathKey } from '../utils/commonUtil';
|
|
||||||
import { useBaseProps } from '../../vc-select';
|
import { useBaseProps } from '../../vc-select';
|
||||||
import KeyCode from '../../_util/KeyCode';
|
import KeyCode from '../../_util/KeyCode';
|
||||||
|
import { SEARCH_MARK } from '../hooks/useSearchOptions';
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
context: SetupContext,
|
context: SetupContext,
|
||||||
|
@ -13,7 +13,7 @@ export default (
|
||||||
fieldNames: Ref<InternalFieldNames>,
|
fieldNames: Ref<InternalFieldNames>,
|
||||||
activeValueCells: Ref<Key[]>,
|
activeValueCells: Ref<Key[]>,
|
||||||
setActiveValueCells: (activeValueCells: Key[]) => void,
|
setActiveValueCells: (activeValueCells: Key[]) => void,
|
||||||
containerRef: Ref<HTMLElement>,
|
// containerRef: Ref<HTMLElement>,
|
||||||
onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void,
|
onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void,
|
||||||
) => {
|
) => {
|
||||||
const baseProps = useBaseProps();
|
const baseProps = useBaseProps();
|
||||||
|
@ -32,7 +32,7 @@ export default (
|
||||||
|
|
||||||
const len = activeValueCells.value.length;
|
const len = activeValueCells.value.length;
|
||||||
// Fill validate active value cells and index
|
// Fill validate active value cells and index
|
||||||
for (let i = 0; i < len; i += 1) {
|
for (let i = 0; i < len && currentOptions; i += 1) {
|
||||||
// Mark the active index for current options
|
// Mark the active index for current options
|
||||||
const nextActiveIndex = currentOptions.findIndex(
|
const nextActiveIndex = currentOptions.findIndex(
|
||||||
option => option[fieldNames.value.value] === activeValueCells.value[i],
|
option => option[fieldNames.value.value] === activeValueCells.value[i],
|
||||||
|
@ -65,9 +65,6 @@ export default (
|
||||||
// Update active value cells and scroll to target element
|
// Update active value cells and scroll to target element
|
||||||
const internalSetActiveValueCells = (next: Key[]) => {
|
const internalSetActiveValueCells = (next: Key[]) => {
|
||||||
setActiveValueCells(next);
|
setActiveValueCells(next);
|
||||||
|
|
||||||
const ele = containerRef.value?.querySelector(`li[data-path-key="${toPathKey(next)}"]`);
|
|
||||||
ele?.scrollIntoView?.({ block: 'nearest' });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same options offset
|
// Same options offset
|
||||||
|
@ -165,10 +162,18 @@ export default (
|
||||||
// >>> Select
|
// >>> Select
|
||||||
case KeyCode.ENTER: {
|
case KeyCode.ENTER: {
|
||||||
if (validActiveValueCells.value.length) {
|
if (validActiveValueCells.value.length) {
|
||||||
onKeyBoardSelect(
|
const option = lastActiveOptions.value[lastActiveIndex.value];
|
||||||
validActiveValueCells.value,
|
|
||||||
lastActiveOptions.value[lastActiveIndex.value],
|
// Search option should revert back of origin options
|
||||||
);
|
const originOptions: DefaultOptionType[] = option?.[SEARCH_MARK] || [];
|
||||||
|
if (originOptions.length) {
|
||||||
|
onKeyBoardSelect(
|
||||||
|
originOptions.map(opt => opt[fieldNames.value.value]),
|
||||||
|
originOptions[originOptions.length - 1],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onKeyBoardSelect(validActiveValueCells.value, option);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,11 @@ export default (
|
||||||
labels: valueOptions.map(({ option, value }) => option?.[fieldNames.value.label] ?? value),
|
labels: valueOptions.map(({ option, value }) => option?.[fieldNames.value.label] ?? value),
|
||||||
selectedOptions: valueOptions.map(({ option }) => option),
|
selectedOptions: valueOptions.map(({ option }) => option),
|
||||||
});
|
});
|
||||||
|
const value = toPathKey(valueCells);
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
value: toPathKey(valueCells),
|
value,
|
||||||
|
key: value,
|
||||||
valueCells,
|
valueCells,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default (
|
||||||
if (
|
if (
|
||||||
// If is leaf option
|
// If is leaf option
|
||||||
!children ||
|
!children ||
|
||||||
|
children.length === 0 ||
|
||||||
// If is changeOnSelect
|
// If is changeOnSelect
|
||||||
changeOnSelect.value
|
changeOnSelect.value
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// rc-cascader@3.0.0-alpha.6
|
// rc-cascader@3.4.2
|
||||||
import Cascader, { internalCascaderProps as cascaderProps } from './Cascader';
|
import Cascader, {
|
||||||
|
internalCascaderProps as cascaderProps,
|
||||||
|
SHOW_PARENT,
|
||||||
|
SHOW_CHILD,
|
||||||
|
} from './Cascader';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
CascaderProps,
|
CascaderProps,
|
||||||
|
@ -8,5 +12,5 @@ export type {
|
||||||
DefaultOptionType,
|
DefaultOptionType,
|
||||||
BaseOptionType,
|
BaseOptionType,
|
||||||
} from './Cascader';
|
} from './Cascader';
|
||||||
export { cascaderProps };
|
export { cascaderProps, SHOW_PARENT, SHOW_CHILD };
|
||||||
export default Cascader;
|
export default Cascader;
|
||||||
|
|
|
@ -6,6 +6,8 @@ import type {
|
||||||
} from '../Cascader';
|
} from '../Cascader';
|
||||||
|
|
||||||
export const VALUE_SPLIT = '__RC_CASCADER_SPLIT__';
|
export const VALUE_SPLIT = '__RC_CASCADER_SPLIT__';
|
||||||
|
export const SHOW_PARENT = 'SHOW_PARENT';
|
||||||
|
export const SHOW_CHILD = 'SHOW_CHILD';
|
||||||
|
|
||||||
export function toPathKey(value: SingleValueType) {
|
export function toPathKey(value: SingleValueType) {
|
||||||
return value.join(VALUE_SPLIT);
|
return value.join(VALUE_SPLIT);
|
||||||
|
@ -33,3 +35,17 @@ export function fillFieldNames(fieldNames?: FieldNames): InternalFieldNames {
|
||||||
export function isLeaf(option: DefaultOptionType, fieldNames: FieldNames) {
|
export function isLeaf(option: DefaultOptionType, fieldNames: FieldNames) {
|
||||||
return option.isLeaf ?? !option[fieldNames.children]?.length;
|
return option.isLeaf ?? !option[fieldNames.children]?.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function scrollIntoParentView(element: HTMLElement) {
|
||||||
|
const parent = element.parentElement;
|
||||||
|
if (!parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementToParent = element.offsetTop - parent.offsetTop; // offsetParent may not be parent.
|
||||||
|
if (elementToParent - parent.scrollTop < 0) {
|
||||||
|
parent.scrollTo({ top: elementToParent });
|
||||||
|
} else if (elementToParent + element.offsetHeight - parent.scrollTop > parent.offsetHeight) {
|
||||||
|
parent.scrollTo({ top: elementToParent + element.offsetHeight - parent.offsetHeight });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
import type { Key } from '../../_util/type';
|
import type { Key } from '../../_util/type';
|
||||||
import type { SingleValueType, DefaultOptionType, InternalFieldNames } from '../Cascader';
|
import type {
|
||||||
|
SingleValueType,
|
||||||
|
DefaultOptionType,
|
||||||
|
InternalFieldNames,
|
||||||
|
ShowCheckedStrategy,
|
||||||
|
} from '../Cascader';
|
||||||
import type { OptionsInfo } from '../hooks/useEntities';
|
import type { OptionsInfo } from '../hooks/useEntities';
|
||||||
|
import { SHOW_CHILD } from './commonUtil';
|
||||||
|
|
||||||
export function formatStrategyValues(
|
export function formatStrategyValues(
|
||||||
pathKeys: Key[],
|
pathKeys: Key[],
|
||||||
keyPathEntities: OptionsInfo['pathKeyEntities'],
|
keyPathEntities: OptionsInfo['pathKeyEntities'],
|
||||||
|
showCheckedStrategy: ShowCheckedStrategy,
|
||||||
) {
|
) {
|
||||||
const valueSet = new Set(pathKeys);
|
const valueSet = new Set(pathKeys);
|
||||||
|
|
||||||
return pathKeys.filter(key => {
|
return pathKeys.filter(key => {
|
||||||
const entity = keyPathEntities[key];
|
const entity = keyPathEntities[key];
|
||||||
const parent = entity ? entity.parent : null;
|
const parent = entity ? entity.parent : null;
|
||||||
|
const children = entity ? entity.children : null;
|
||||||
if (parent && !parent.node.disabled && valueSet.has(parent.key)) {
|
return showCheckedStrategy === SHOW_CHILD
|
||||||
return false;
|
? !(children && children.some(child => child.key && valueSet.has(child.key)))
|
||||||
}
|
: !(parent && !parent.node.disabled && valueSet.has(parent.key));
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue