refactor: select by vc-select2

feat-dayjs
tanjinzhou 2020-10-16 18:47:28 +08:00
parent f3c12b0323
commit 4d63b7cab6
9 changed files with 616 additions and 348 deletions

View File

@ -1,6 +1,6 @@
import { App, defineComponent, inject, provide } from 'vue';
import { Option, OptGroup } from '../vc-select';
import Select, { AbstractSelectProps, SelectValue } from '../select';
import Select, { SelectProps } from '../select';
import Input from '../input';
import InputElement from './InputElement';
import PropTypes from '../_util/vue-types';
@ -12,9 +12,7 @@ function isSelectOptionOrSelectOptGroup(child: any): Boolean {
}
const AutoCompleteProps = {
...AbstractSelectProps(),
value: SelectValue,
defaultValue: SelectValue,
...SelectProps,
dataSource: PropTypes.array,
dropdownMenuStyle: PropTypes.style,
optionLabelProp: PropTypes.string,

View File

@ -1,4 +1,4 @@
import { inject } from 'vue';
import { inject, VNodeChild } from 'vue';
import Empty from '../empty';
import { defaultConfigProvider } from '.';
@ -30,7 +30,7 @@ const RenderEmpty = (props: RenderEmptyProps) => {
return renderHtml(props.componentName);
};
function renderEmpty(componentName?: string) {
function renderEmpty(componentName?: string): VNodeChild | JSX.Element{
return <RenderEmpty componentName={componentName} />;
}

View File

@ -1,268 +1,204 @@
import { provide, inject, defineComponent, App } from 'vue';
import warning from '../_util/warning';
import omit from 'omit.js';
import PropTypes, { withUndefined } from '../_util/vue-types';
import { Select as VcSelect, Option, OptGroup } from '../vc-select';
import classNames from '../_util/classNames';
import RcSelect, { Option, OptGroup, SelectProps as RcSelectProps, props } from '../vc-select2';
import { OptionProps } from '../vc-select2/Option';
import { defaultConfigProvider } from '../config-provider';
import { getComponent, getOptionProps, isValidElement, getSlot } from '../_util/props-util';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import CheckOutlined from '@ant-design/icons-vue/CheckOutlined';
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import { cloneElement } from '../_util/vnode';
import getIcons from './utils/iconUtil';
import { computed, defineComponent, inject, ref, VNodeChild, App, PropType } from 'vue';
import PropTypes from '../_util/vue-types';
import { tuple } from '../_util/type';
const AbstractSelectProps = () => ({
prefixCls: PropTypes.string,
size: PropTypes.oneOf(['small', 'large', 'default']),
showAction: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(String)]),
notFoundContent: PropTypes.VNodeChild,
transitionName: PropTypes.string,
choiceTransitionName: PropTypes.string,
showSearch: PropTypes.looseBool,
allowClear: PropTypes.looseBool,
disabled: PropTypes.looseBool,
tabindex: PropTypes.number,
placeholder: PropTypes.VNodeChild,
defaultActiveFirstOption: PropTypes.looseBool,
dropdownClassName: PropTypes.string,
dropdownStyle: PropTypes.style,
dropdownMenuStyle: PropTypes.style,
dropdownMatchSelectWidth: PropTypes.looseBool,
// onSearch: (value: string) => any,
filterOption: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.func])),
autofocus: PropTypes.looseBool,
backfill: PropTypes.looseBool,
showArrow: PropTypes.looseBool,
getPopupContainer: PropTypes.func,
open: PropTypes.looseBool,
defaultOpen: PropTypes.looseBool,
autoClearSearchValue: PropTypes.looseBool,
dropdownRender: PropTypes.func,
onChange: PropTypes.func,
loading: PropTypes.looseBool,
});
const Value = PropTypes.shape({
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}).loose;
type RawValue = string | number;
const SelectValue = PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.arrayOf(PropTypes.oneOfType([Value, PropTypes.string, PropTypes.number])),
Value,
]);
export { OptionProps };
const SelectProps = {
...AbstractSelectProps(),
value: SelectValue,
defaultValue: SelectValue,
// mode: PropTypes.oneOf(['default', 'multiple', 'tags', 'combobox']),
mode: PropTypes.string,
optionLabelProp: PropTypes.string,
firstActiveValue: PropTypes.oneOfType([String, PropTypes.arrayOf(String)]),
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
maxTagTextLength: PropTypes.number,
dropdownMatchSelectWidth: PropTypes.looseBool,
optionFilterProp: PropTypes.string,
labelInValue: PropTypes.looseBool,
getPopupContainer: PropTypes.func,
tokenSeparators: PropTypes.arrayOf(PropTypes.string),
getInputElement: PropTypes.func,
options: PropTypes.array,
suffixIcon: PropTypes.any,
removeIcon: PropTypes.any,
clearIcon: PropTypes.any,
menuItemSelectedIcon: PropTypes.any,
};
export type OptionType = typeof Option;
const SelectPropTypes = {
prefixCls: PropTypes.string,
size: PropTypes.oneOf(['default', 'large', 'small']),
// combobox: PropTypes.looseBool,
notFoundContent: PropTypes.any,
showSearch: PropTypes.looseBool,
optionLabelProp: PropTypes.string,
transitionName: PropTypes.string,
choiceTransitionName: PropTypes.string,
};
export interface LabeledValue {
key?: string;
value: RawValue;
label: VNodeChild;
}
export type SizeType = 'small' | 'middle' | 'large' | undefined;
export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[];
export interface InternalSelectProps<VT> extends Omit<RcSelectProps<VT>, 'mode'> {
suffixIcon?: VNodeChild;
itemIcon?: VNodeChild;
size?: SizeType;
mode?: 'multiple' | 'tags' | 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
bordered?: boolean;
}
export interface SelectPropsTypes<VT>
extends Omit<InternalSelectProps<VT>, 'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style'> {
mode?: 'multiple' | 'tags';
}
export type SelectTypes = SelectPropsTypes<SelectValue>
export const SelectProps = {
...omit(props, ['inputIcon' ,'mode' ,'getInputElement' ,'backfill' ,'class' ,'style']),
value: {
type: [Array, Object, String, Number] as PropType<SelectValue>
},
defaultValue: {
type: [Array, Object, String, Number] as PropType<SelectValue>
},
suffixIcon: PropTypes.VNodeChild,
itemIcon: PropTypes.VNodeChild,
size: PropTypes.oneOf(tuple('small', 'middle', 'large', undefined, 'default')),
mode: PropTypes.oneOf(tuple('multiple', 'tags')),
bordered: PropTypes.looseBool.def(true),
transitionName: PropTypes.string.def('slide-up'),
choiceTransitionName: PropTypes.string.def(''),
}
export { AbstractSelectProps, SelectValue, SelectProps };
const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
const Select = defineComponent({
SECRET_COMBOBOX_MODE_DO_NOT_USE,
Option: { ...Option, name: 'ASelectOption' },
OptGroup: { ...OptGroup, name: 'ASelectOptGroup' },
name: 'ASelect',
props: {
...SelectProps,
showSearch: PropTypes.looseBool.def(false),
transitionName: PropTypes.string.def('slide-up'),
choiceTransitionName: PropTypes.string.def('zoom'),
},
propTypes: SelectPropTypes,
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
popupRef: null,
Option,
OptGroup,
inheritAttrs: false,
props: SelectProps,
SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE',
emits: ['change', 'update:value'],
setup(props: any, {attrs, emit}) {
const selectRef = ref(null);
const configProvider = inject('configProvider', defaultConfigProvider);
const focus = () => {
if (selectRef.value) {
selectRef.value.focus();
}
};
},
created() {
provide('savePopupRef', this.savePopupRef);
warning(
this.$props.mode !== 'combobox',
'Select',
'The combobox mode of Select is deprecated,' +
'it will be removed in next major version,' +
'please use AutoComplete instead',
);
},
methods: {
getNotFoundContent(renderEmpty: any) {
const notFoundContent = getComponent(this, 'notFoundContent');
if (notFoundContent !== undefined) {
return notFoundContent;
}
if (this.isCombobox()) {
return null;
}
return renderEmpty('Select');
},
savePopupRef(ref) {
this.popupRef = ref;
},
focus() {
(this.$refs.vcSelect as any).focus();
},
blur() {
(this.$refs.vcSelect as any).blur();
},
isCombobox() {
const { mode } = this;
return mode === 'combobox' || mode === SECRET_COMBOBOX_MODE_DO_NOT_USE;
},
const blur = () => {
if (selectRef.value) {
selectRef.value.blur();
}
};
renderSuffixIcon(prefixCls) {
const { loading } = this.$props;
let suffixIcon = getComponent(this, 'suffixIcon');
suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon;
if (suffixIcon) {
return isValidElement(suffixIcon)
? cloneElement(suffixIcon, { class: `${prefixCls}-arrow-icon` })
: suffixIcon;
const mode = computed(()=>{
const { mode } = props
if ((mode as any) === 'combobox') {
return undefined;
}
if (loading) {
return <LoadingOutlined />;
if (mode === Select.SECRET_COMBOBOX_MODE_DO_NOT_USE) {
return 'combobox';
}
return <DownOutlined class={`${prefixCls}-arrow-icon`} />;
},
return mode;
});
const mergedClassName = computed(()=> classNames(
{
[`${props.prefixCls}-lg`]: props.size === 'large',
[`${props.prefixCls}-sm`]: props.size === 'small',
[`${props.prefixCls}-rtl`]: props.direction === 'rtl',
[`${props.prefixCls}-borderless`]: !props.bordered,
},
attrs.class,
));
const triggerChange=(...args: any[])=>{
console.log(args)
emit('update:value', ...args)
emit('change', ...args)
}
return {
mergedClassName,
mode,
focus,
blur,
configProvider,
triggerChange
}
},
render() {
const {configProvider, mode, mergedClassName,triggerChange, $slots: slots, $props} = this as any;
const props: SelectTypes = $props
const {
prefixCls: customizePrefixCls,
size,
mode,
options,
notFoundContent,
listHeight = 256,
listItemHeight = 24,
getPopupContainer,
showArrow,
...restProps
} = getOptionProps(this);
const { class: className } = this.$attrs as any;
dropdownClassName,
direction,
virtual,
dropdownMatchSelectWidth
} = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const renderEmpty = this.configProvider.renderEmpty;
const { getPrefixCls, renderEmpty, getPopupContainer: getContextPopupContainer } = configProvider
const prefixCls = getPrefixCls('select', customizePrefixCls);
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
let removeIcon = getComponent(this, 'removeIcon');
removeIcon = Array.isArray(removeIcon) ? removeIcon[0] : removeIcon;
let clearIcon = getComponent(this, 'clearIcon');
clearIcon = Array.isArray(clearIcon) ? clearIcon[0] : clearIcon;
let menuItemSelectedIcon = getComponent(this, 'menuItemSelectedIcon');
menuItemSelectedIcon = Array.isArray(menuItemSelectedIcon)
? menuItemSelectedIcon[0]
: menuItemSelectedIcon;
const rest = omit(restProps as any, [
'inputIcon',
'removeIcon',
'clearIcon',
'suffixIcon',
'menuItemSelectedIcon',
]);
const isMultiple = mode === 'multiple' || mode === 'tags';
const cls = {
[className]: className,
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-show-arrow`]: showArrow,
};
let { optionLabelProp } = this.$props;
if (this.isCombobox()) {
// children 带 dom 结构时,无法填入输入框
optionLabelProp = optionLabelProp || 'value';
// ===================== Empty =====================
let mergedNotFound: VNodeChild;
if (notFoundContent !== undefined) {
mergedNotFound = notFoundContent;
} else if(slots.notFoundContent){
mergedNotFound = slots.notFoundContent()
} else if (mode === 'combobox') {
mergedNotFound = null;
} else {
mergedNotFound = renderEmpty('Select') as any;
}
const modeConfig = {
multiple: mode === 'multiple',
tags: mode === 'tags',
combobox: this.isCombobox(),
};
const finalRemoveIcon = (removeIcon &&
(isValidElement(removeIcon)
? cloneElement(removeIcon, { class: `${prefixCls}-remove-icon` })
: removeIcon)) || <CloseOutlined class={`${prefixCls}-remove-icon`} />;
const finalClearIcon = (clearIcon &&
(isValidElement(clearIcon)
? cloneElement(clearIcon, { class: `${prefixCls}-clear-icon` })
: clearIcon)) || <CloseCircleFilled class={`${prefixCls}-clear-icon`} />;
const finalMenuItemSelectedIcon = (menuItemSelectedIcon &&
(isValidElement(menuItemSelectedIcon)
? cloneElement(menuItemSelectedIcon, { class: `${prefixCls}-selected-icon` })
: menuItemSelectedIcon)) || <CheckOutlined class={`${prefixCls}-selected-icon`} />;
const selectProps = {
inputIcon: this.renderSuffixIcon(prefixCls),
removeIcon: finalRemoveIcon,
clearIcon: finalClearIcon,
menuItemSelectedIcon: finalMenuItemSelectedIcon,
showArrow,
...rest,
...modeConfig,
// ===================== Icons =====================
const { suffixIcon, itemIcon, removeIcon, clearIcon } = getIcons({
...this.$props,
multiple: isMultiple,
prefixCls,
optionLabelProp: optionLabelProp || 'children',
notFoundContent: this.getNotFoundContent(renderEmpty),
maxTagPlaceholder: getComponent(this, 'maxTagPlaceholder'),
placeholder: getComponent(this, 'placeholder'),
children: options
? options.map(option => {
const { key, label = option.title, class: cls, style, ...restOption } = option;
return (
<Option key={key} {...{ class: cls, style, ...restOption }}>
{label}
</Option>
);
})
: getSlot(this),
dropdownRender: getComponent(this, 'dropdownRender', {}, false),
getPopupContainer: getPopupContainer || getContextPopupContainer,
...this.$attrs,
class: cls,
ref: 'vcSelect',
};
return <VcSelect {...selectProps} __propsSymbol__={[]} />;
},
});
}, slots);
const selectProps = omit(props, [
'prefixCls',
'suffixIcon',
'itemIcon',
'removeIcon',
'clearIcon',
'size',
'bordered',
]) as any;
const rcSelectRtlDropDownClassName = classNames(dropdownClassName, {
[`${prefixCls}-dropdown-${direction}`]: direction === 'rtl',
});
<RcSelect
ref="selectRef"
virtual={virtual}
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
{...selectProps}
listHeight={listHeight}
listItemHeight={listItemHeight}
mode={mode}
prefixCls={prefixCls}
direction={direction}
inputIcon={suffixIcon}
menuItemSelectedIcon={itemIcon}
removeIcon={removeIcon}
clearIcon={clearIcon}
notFoundContent={mergedNotFound}
class={mergedClassName}
getPopupContainer={getPopupContainer || getContextPopupContainer}
dropdownClassName={rcSelectRtlDropDownClassName}
onChange={triggerChange}
>
{slots?.default()}
</RcSelect>
}
})
/* istanbul ignore next */
Select.install = function(app: App) {
app.component(Select.name, Select);
app.component(Select.Option.name, Select.Option);
app.component(Select.OptGroup.name, Select.OptGroup);
app.component('ASelectOption', Select.Option);
app.component('ASelectOptGroup', Select.OptGroup);
return app;
};
export default Select;
export default Select as typeof Select & {
readonly Option: typeof Option;
readonly OptGroup: typeof OptGroup;
SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
};

View File

@ -0,0 +1,268 @@
import { provide, inject, defineComponent, App } from 'vue';
import warning from '../_util/warning';
import omit from 'omit.js';
import PropTypes, { withUndefined } from '../_util/vue-types';
import { Select as VcSelect, Option, OptGroup } from '../vc-select';
import { defaultConfigProvider } from '../config-provider';
import { getComponent, getOptionProps, isValidElement, getSlot } from '../_util/props-util';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import CheckOutlined from '@ant-design/icons-vue/CheckOutlined';
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import { cloneElement } from '../_util/vnode';
const AbstractSelectProps = () => ({
prefixCls: PropTypes.string,
size: PropTypes.oneOf(['small', 'large', 'default']),
showAction: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(String)]),
notFoundContent: PropTypes.VNodeChild,
transitionName: PropTypes.string,
choiceTransitionName: PropTypes.string,
showSearch: PropTypes.looseBool,
allowClear: PropTypes.looseBool,
disabled: PropTypes.looseBool,
tabindex: PropTypes.number,
placeholder: PropTypes.VNodeChild,
defaultActiveFirstOption: PropTypes.looseBool,
dropdownClassName: PropTypes.string,
dropdownStyle: PropTypes.style,
dropdownMenuStyle: PropTypes.style,
dropdownMatchSelectWidth: PropTypes.looseBool,
// onSearch: (value: string) => any,
filterOption: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.func])),
autofocus: PropTypes.looseBool,
backfill: PropTypes.looseBool,
showArrow: PropTypes.looseBool,
getPopupContainer: PropTypes.func,
open: PropTypes.looseBool,
defaultOpen: PropTypes.looseBool,
autoClearSearchValue: PropTypes.looseBool,
dropdownRender: PropTypes.func,
onChange: PropTypes.func,
loading: PropTypes.looseBool,
});
const Value = PropTypes.shape({
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}).loose;
const SelectValue = PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.arrayOf(PropTypes.oneOfType([Value, PropTypes.string, PropTypes.number])),
Value,
]);
const SelectProps = {
...AbstractSelectProps(),
value: SelectValue,
defaultValue: SelectValue,
// mode: PropTypes.oneOf(['default', 'multiple', 'tags', 'combobox']),
mode: PropTypes.string,
optionLabelProp: PropTypes.string,
firstActiveValue: PropTypes.oneOfType([String, PropTypes.arrayOf(String)]),
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
maxTagTextLength: PropTypes.number,
dropdownMatchSelectWidth: PropTypes.looseBool,
optionFilterProp: PropTypes.string,
labelInValue: PropTypes.looseBool,
getPopupContainer: PropTypes.func,
tokenSeparators: PropTypes.arrayOf(PropTypes.string),
getInputElement: PropTypes.func,
options: PropTypes.array,
suffixIcon: PropTypes.any,
removeIcon: PropTypes.any,
clearIcon: PropTypes.any,
menuItemSelectedIcon: PropTypes.any,
};
const SelectPropTypes = {
prefixCls: PropTypes.string,
size: PropTypes.oneOf(['default', 'large', 'small']),
// combobox: PropTypes.looseBool,
notFoundContent: PropTypes.any,
showSearch: PropTypes.looseBool,
optionLabelProp: PropTypes.string,
transitionName: PropTypes.string,
choiceTransitionName: PropTypes.string,
};
export { AbstractSelectProps, SelectValue, SelectProps };
const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
const Select = defineComponent({
SECRET_COMBOBOX_MODE_DO_NOT_USE,
Option: { ...Option, name: 'ASelectOption' },
OptGroup: { ...OptGroup, name: 'ASelectOptGroup' },
name: 'ASelect',
props: {
...SelectProps,
showSearch: PropTypes.looseBool.def(false),
transitionName: PropTypes.string.def('slide-up'),
choiceTransitionName: PropTypes.string.def('zoom'),
},
propTypes: SelectPropTypes,
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
popupRef: null,
};
},
created() {
provide('savePopupRef', this.savePopupRef);
warning(
this.$props.mode !== 'combobox',
'Select',
'The combobox mode of Select is deprecated,' +
'it will be removed in next major version,' +
'please use AutoComplete instead',
);
},
methods: {
getNotFoundContent(renderEmpty: any) {
const notFoundContent = getComponent(this, 'notFoundContent');
if (notFoundContent !== undefined) {
return notFoundContent;
}
if (this.isCombobox()) {
return null;
}
return renderEmpty('Select');
},
savePopupRef(ref) {
this.popupRef = ref;
},
focus() {
(this.$refs.vcSelect as any).focus();
},
blur() {
(this.$refs.vcSelect as any).blur();
},
isCombobox() {
const { mode } = this;
return mode === 'combobox' || mode === SECRET_COMBOBOX_MODE_DO_NOT_USE;
},
renderSuffixIcon(prefixCls) {
const { loading } = this.$props;
let suffixIcon = getComponent(this, 'suffixIcon');
suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon;
if (suffixIcon) {
return isValidElement(suffixIcon)
? cloneElement(suffixIcon, { class: `${prefixCls}-arrow-icon` })
: suffixIcon;
}
if (loading) {
return <LoadingOutlined />;
}
return <DownOutlined class={`${prefixCls}-arrow-icon`} />;
},
},
render() {
const {
prefixCls: customizePrefixCls,
size,
mode,
options,
getPopupContainer,
showArrow,
...restProps
} = getOptionProps(this);
const { class: className } = this.$attrs as any;
const getPrefixCls = this.configProvider.getPrefixCls;
const renderEmpty = this.configProvider.renderEmpty;
const prefixCls = getPrefixCls('select', customizePrefixCls);
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
let removeIcon = getComponent(this, 'removeIcon');
removeIcon = Array.isArray(removeIcon) ? removeIcon[0] : removeIcon;
let clearIcon = getComponent(this, 'clearIcon');
clearIcon = Array.isArray(clearIcon) ? clearIcon[0] : clearIcon;
let menuItemSelectedIcon = getComponent(this, 'menuItemSelectedIcon');
menuItemSelectedIcon = Array.isArray(menuItemSelectedIcon)
? menuItemSelectedIcon[0]
: menuItemSelectedIcon;
const rest = omit(restProps as any, [
'inputIcon',
'removeIcon',
'clearIcon',
'suffixIcon',
'menuItemSelectedIcon',
]);
const cls = {
[className]: className,
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-show-arrow`]: showArrow,
};
let { optionLabelProp } = this.$props;
if (this.isCombobox()) {
// children 带 dom 结构时,无法填入输入框
optionLabelProp = optionLabelProp || 'value';
}
const modeConfig = {
multiple: mode === 'multiple',
tags: mode === 'tags',
combobox: this.isCombobox(),
};
const finalRemoveIcon = (removeIcon &&
(isValidElement(removeIcon)
? cloneElement(removeIcon, { class: `${prefixCls}-remove-icon` })
: removeIcon)) || <CloseOutlined class={`${prefixCls}-remove-icon`} />;
const finalClearIcon = (clearIcon &&
(isValidElement(clearIcon)
? cloneElement(clearIcon, { class: `${prefixCls}-clear-icon` })
: clearIcon)) || <CloseCircleFilled class={`${prefixCls}-clear-icon`} />;
const finalMenuItemSelectedIcon = (menuItemSelectedIcon &&
(isValidElement(menuItemSelectedIcon)
? cloneElement(menuItemSelectedIcon, { class: `${prefixCls}-selected-icon` })
: menuItemSelectedIcon)) || <CheckOutlined class={`${prefixCls}-selected-icon`} />;
const selectProps = {
inputIcon: this.renderSuffixIcon(prefixCls),
removeIcon: finalRemoveIcon,
clearIcon: finalClearIcon,
menuItemSelectedIcon: finalMenuItemSelectedIcon,
showArrow,
...rest,
...modeConfig,
prefixCls,
optionLabelProp: optionLabelProp || 'children',
notFoundContent: this.getNotFoundContent(renderEmpty),
maxTagPlaceholder: getComponent(this, 'maxTagPlaceholder'),
placeholder: getComponent(this, 'placeholder'),
children: options
? options.map(option => {
const { key, label = option.title, class: cls, style, ...restOption } = option;
return (
<Option key={key} {...{ class: cls, style, ...restOption }}>
{label}
</Option>
);
})
: getSlot(this),
dropdownRender: getComponent(this, 'dropdownRender', {}, false),
getPopupContainer: getPopupContainer || getContextPopupContainer,
...this.$attrs,
class: cls,
ref: 'vcSelect',
};
return <VcSelect {...selectProps} __propsSymbol__={[]} />;
},
});
/* istanbul ignore next */
Select.install = function(app: App) {
app.component(Select.name, Select);
app.component(Select.Option.name, Select.Option);
app.component(Select.OptGroup.name, Select.OptGroup);
return app;
};
export default Select;

View File

@ -0,0 +1,64 @@
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import CheckOutlined from '@ant-design/icons-vue/CheckOutlined';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
export default function getIcons(props: any, slots: any = {}) {
const {
loading,
multiple,
prefixCls,
} = props;
const suffixIcon = props.suffixIcon || slots.suffixIcon && slots.suffixIcon();
const clearIcon = props.clearIcon || slots.clearIcon && slots.clearIcon();
const menuItemSelectedIcon = props.menuItemSelectedIcon || slots.menuItemSelectedIcon && slots.menuItemSelectedIcon();
const removeIcon = props.removeIcon || slots.removeIcon && slots.removeIcon();
// Clear Icon
let mergedClearIcon = clearIcon;
if (!clearIcon) {
mergedClearIcon = <CloseCircleFilled />;
}
// Arrow item icon
let mergedSuffixIcon = null;
if (suffixIcon !== undefined) {
mergedSuffixIcon = suffixIcon;
} else if (loading) {
mergedSuffixIcon = <LoadingOutlined spin />;
} else {
const iconCls = `${prefixCls}-suffix`;
mergedSuffixIcon = ({ open, showSearch }: { open: boolean; showSearch: boolean }) => {
if (open && showSearch) {
return <SearchOutlined class={iconCls} />;
}
return <DownOutlined class={iconCls} />;
};
}
// Checked item icon
let mergedItemIcon = null;
if (menuItemSelectedIcon !== undefined) {
mergedItemIcon = menuItemSelectedIcon;
} else if (multiple) {
mergedItemIcon = <CheckOutlined />;
} else {
mergedItemIcon = null;
}
let mergedRemoveIcon = null;
if (removeIcon !== undefined) {
mergedRemoveIcon = removeIcon;
} else {
mergedRemoveIcon = <CloseOutlined />;
}
return {
clearIcon: mergedClearIcon,
suffixIcon: mergedSuffixIcon,
itemIcon: mergedItemIcon,
removeIcon: mergedRemoveIcon,
};
}

View File

@ -1,5 +1,5 @@
import PropTypes, { withUndefined } from '../_util/vue-types';
import { AbstractSelectProps } from '../select';
import { SelectProps } from '../select';
export const TreeData = PropTypes.shape({
key: PropTypes.string,
@ -10,7 +10,7 @@ export const TreeData = PropTypes.shape({
}).loose;
export const TreeSelectProps = () => ({
...AbstractSelectProps(),
...SelectProps,
autofocus: PropTypes.looseBool,
dropdownStyle: PropTypes.object,
filterTreeNode: withUndefined(PropTypes.oneOfType([Function, Boolean])),

View File

@ -65,6 +65,106 @@ const DEFAULT_OMIT_PROPS = [
'onInputKeyDown',
];
export const props = {
prefixCls: PropTypes.string,
id: PropTypes.string,
class: PropTypes.string,
style: PropTypes.any,
// Options
options: PropTypes.array,
children: PropTypes.array.def([]),
mode: PropTypes.string,
// Value
value: PropTypes.any,
defaultValue: PropTypes.any,
labelInValue: PropTypes.looseBool,
// Search
inputValue: PropTypes.string,
searchValue: PropTypes.string,
optionFilterProp: PropTypes.string.def('value'),
/**
* In Select, `false` means do nothing.
* In TreeSelect, `false` will highlight match item.
* It's by design.
*/
filterOption: PropTypes.any,
showSearch: PropTypes.looseBool,
autoClearSearchValue: PropTypes.looseBool,
onSearch: PropTypes.func,
onClear: PropTypes.func,
// Icons
allowClear: PropTypes.looseBool,
clearIcon: PropTypes.any,
showArrow: PropTypes.looseBool,
inputIcon: PropTypes.any,
removeIcon: PropTypes.any,
menuItemSelectedIcon: PropTypes.func,
// Dropdown
open: PropTypes.looseBool,
defaultOpen: PropTypes.looseBool,
listHeight: PropTypes.number.def(200),
listItemHeight: PropTypes.number.def(20),
dropdownStyle: PropTypes.object,
dropdownClassName: PropTypes.string,
dropdownMatchSelectWidth: PropTypes.oneOfType([Boolean, Number]).def(true),
virtual: PropTypes.looseBool,
dropdownRender: PropTypes.func,
dropdownAlign: PropTypes.any,
animation: PropTypes.string,
transitionName: PropTypes.string,
getPopupContainer: PropTypes.func,
direction: PropTypes.string,
// Others
disabled: PropTypes.looseBool,
loading: PropTypes.looseBool,
autofocus: PropTypes.looseBool,
defaultActiveFirstOption: PropTypes.looseBool,
notFoundContent: PropTypes.any.def('Not Found'),
placeholder: PropTypes.any,
backfill: PropTypes.looseBool,
getInputElement: PropTypes.func,
optionLabelProp: PropTypes.string,
maxTagTextLength: PropTypes.number,
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
tokenSeparators: PropTypes.array,
tagRender: PropTypes.func,
showAction: PropTypes.array.def([]),
tabindex: PropTypes.number,
// Events
onKeyup: PropTypes.func,
onKeydown: PropTypes.func,
onPopupScroll: PropTypes.func,
onDropdownVisibleChange: PropTypes.func,
onSelect: PropTypes.func,
onDeselect: PropTypes.func,
onInputKeyDown: PropTypes.func,
onClick: PropTypes.func,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
onMousedown: PropTypes.func,
onMouseenter: PropTypes.func,
onMouseleave: PropTypes.func,
// Motion
choiceTransitionName: PropTypes.string,
// Internal props
/**
* Only used in current version for internal event process.
* Do not use in production environment.
*/
internalProps: PropTypes.object.def({}),
}
export interface SelectProps<OptionsType extends object[], ValueType> {
prefixCls?: string;
id?: string;
@ -1221,104 +1321,6 @@ export default function generateSelector<
},
});
Select.inheritAttrs = false;
Select.props = {
prefixCls: PropTypes.string,
id: PropTypes.string,
class: PropTypes.string,
style: PropTypes.any,
// Options
options: PropTypes.array,
children: PropTypes.array.def([]),
mode: PropTypes.string,
// Value
value: PropTypes.any,
defaultValue: PropTypes.any,
labelInValue: PropTypes.looseBool,
// Search
inputValue: PropTypes.string,
searchValue: PropTypes.string,
optionFilterProp: PropTypes.string.def('value'),
/**
* In Select, `false` means do nothing.
* In TreeSelect, `false` will highlight match item.
* It's by design.
*/
filterOption: PropTypes.any,
showSearch: PropTypes.looseBool,
autoClearSearchValue: PropTypes.looseBool,
onSearch: PropTypes.func,
onClear: PropTypes.func,
// Icons
allowClear: PropTypes.looseBool,
clearIcon: PropTypes.any,
showArrow: PropTypes.looseBool,
inputIcon: PropTypes.any,
removeIcon: PropTypes.any,
menuItemSelectedIcon: PropTypes.func,
// Dropdown
open: PropTypes.looseBool,
defaultOpen: PropTypes.looseBool,
listHeight: PropTypes.number.def(200),
listItemHeight: PropTypes.number.def(20),
dropdownStyle: PropTypes.object,
dropdownClassName: PropTypes.string,
dropdownMatchSelectWidth: PropTypes.oneOfType([Boolean, Number]).def(true),
virtual: PropTypes.looseBool,
dropdownRender: PropTypes.func,
dropdownAlign: PropTypes.any,
animation: PropTypes.string,
transitionName: PropTypes.string,
getPopupContainer: PropTypes.func,
direction: PropTypes.string,
// Others
disabled: PropTypes.looseBool,
loading: PropTypes.looseBool,
autofocus: PropTypes.looseBool,
defaultActiveFirstOption: PropTypes.looseBool,
notFoundContent: PropTypes.any.def('Not Found'),
placeholder: PropTypes.any,
backfill: PropTypes.looseBool,
getInputElement: PropTypes.func,
optionLabelProp: PropTypes.string,
maxTagTextLength: PropTypes.number,
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
tokenSeparators: PropTypes.array,
tagRender: PropTypes.func,
showAction: PropTypes.array.def([]),
tabindex: PropTypes.number,
// Events
onKeyup: PropTypes.func,
onKeydown: PropTypes.func,
onPopupScroll: PropTypes.func,
onDropdownVisibleChange: PropTypes.func,
onSelect: PropTypes.func,
onDeselect: PropTypes.func,
onInputKeyDown: PropTypes.func,
onClick: PropTypes.func,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
onMousedown: PropTypes.func,
onMouseenter: PropTypes.func,
onMouseleave: PropTypes.func,
// Motion
choiceTransitionName: PropTypes.string,
// Internal props
/**
* Only used in current version for internal event process.
* Do not use in production environment.
*/
internalProps: PropTypes.object.def({}),
};
Select.props = props;
return Select;
}

View File

@ -1,7 +1,7 @@
import Select, { ExportedSelectProps } from './Select';
import Select, { ExportedSelectProps as SelectProps } from './Select';
import Option from './Option';
import OptGroup from './OptGroup';
type SelectProps = ExportedSelectProps;
export { Option, OptGroup, SelectProps };
import { props } from './generate';
export { Option, OptGroup, SelectProps, props };
export default Select;

View File

@ -1,4 +1,4 @@
import * as Vue from 'vue';
import { VNodeChild } from 'vue';
export type SelectSource = 'option' | 'selection' | 'input';
@ -12,7 +12,7 @@ export type RawValueType = string | number | null;
export interface LabelValueType extends Record<string, any> {
key?: Key;
value?: RawValueType;
label?: Vue.VNodeChild;
label?: VNodeChild;
}
export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];