diff --git a/components/auto-complete/index.tsx b/components/auto-complete/index.tsx index b3e0fba76..08c2063b2 100644 --- a/components/auto-complete/index.tsx +++ b/components/auto-complete/index.tsx @@ -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, diff --git a/components/config-provider/renderEmpty.tsx b/components/config-provider/renderEmpty.tsx index 0dbc8ec95..9692ce0eb 100644 --- a/components/config-provider/renderEmpty.tsx +++ b/components/config-provider/renderEmpty.tsx @@ -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 ; } diff --git a/components/select/index.tsx b/components/select/index.tsx index b7388283b..84592ea11 100644 --- a/components/select/index.tsx +++ b/components/select/index.tsx @@ -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 extends Omit, 'mode'> { + suffixIcon?: VNodeChild; + itemIcon?: VNodeChild; + size?: SizeType; + mode?: 'multiple' | 'tags' | 'SECRET_COMBOBOX_MODE_DO_NOT_USE'; + bordered?: boolean; +} + +export interface SelectPropsTypes + extends Omit, 'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style'> { + mode?: 'multiple' | 'tags'; +} +export type SelectTypes = SelectPropsTypes +export const SelectProps = { + ...omit(props, ['inputIcon' ,'mode' ,'getInputElement' ,'backfill' ,'class' ,'style']), + value: { + type: [Array, Object, String, Number] as PropType + }, + defaultValue: { + type: [Array, Object, String, Number] as PropType + }, + 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 ; + + if (mode === Select.SECRET_COMBOBOX_MODE_DO_NOT_USE) { + return 'combobox'; } - return ; - }, + + 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)) || ; - - const finalClearIcon = (clearIcon && - (isValidElement(clearIcon) - ? cloneElement(clearIcon, { class: `${prefixCls}-clear-icon` }) - : clearIcon)) || ; - - const finalMenuItemSelectedIcon = (menuItemSelectedIcon && - (isValidElement(menuItemSelectedIcon) - ? cloneElement(menuItemSelectedIcon, { class: `${prefixCls}-selected-icon` }) - : menuItemSelectedIcon)) || ; - - 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 ( - - ); - }) - : getSlot(this), - dropdownRender: getComponent(this, 'dropdownRender', {}, false), - getPopupContainer: getPopupContainer || getContextPopupContainer, - ...this.$attrs, - class: cls, - ref: 'vcSelect', - }; - return ; - }, -}); + }, slots); + const selectProps = omit(props, [ + 'prefixCls', + 'suffixIcon', + 'itemIcon', + 'removeIcon', + 'clearIcon', + 'size', + 'bordered', + ]) as any; + + const rcSelectRtlDropDownClassName = classNames(dropdownClassName, { + [`${prefixCls}-dropdown-${direction}`]: direction === 'rtl', + }); + + {slots?.default()} + + } +}) /* 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'; +}; diff --git a/components/select/index1.tsx b/components/select/index1.tsx new file mode 100644 index 000000000..b7388283b --- /dev/null +++ b/components/select/index1.tsx @@ -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 ; + } + return ; + }, + }, + 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)) || ; + + const finalClearIcon = (clearIcon && + (isValidElement(clearIcon) + ? cloneElement(clearIcon, { class: `${prefixCls}-clear-icon` }) + : clearIcon)) || ; + + const finalMenuItemSelectedIcon = (menuItemSelectedIcon && + (isValidElement(menuItemSelectedIcon) + ? cloneElement(menuItemSelectedIcon, { class: `${prefixCls}-selected-icon` }) + : menuItemSelectedIcon)) || ; + + 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 ( + + ); + }) + : getSlot(this), + dropdownRender: getComponent(this, 'dropdownRender', {}, false), + getPopupContainer: getPopupContainer || getContextPopupContainer, + ...this.$attrs, + class: cls, + ref: 'vcSelect', + }; + return ; + }, +}); + +/* 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; diff --git a/components/select/utils/iconUtil.tsx b/components/select/utils/iconUtil.tsx new file mode 100644 index 000000000..ce55393c9 --- /dev/null +++ b/components/select/utils/iconUtil.tsx @@ -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 = ; + } + + // Arrow item icon + let mergedSuffixIcon = null; + if (suffixIcon !== undefined) { + mergedSuffixIcon = suffixIcon; + } else if (loading) { + mergedSuffixIcon = ; + } else { + const iconCls = `${prefixCls}-suffix`; + mergedSuffixIcon = ({ open, showSearch }: { open: boolean; showSearch: boolean }) => { + if (open && showSearch) { + return ; + } + return ; + }; + } + + // Checked item icon + let mergedItemIcon = null; + if (menuItemSelectedIcon !== undefined) { + mergedItemIcon = menuItemSelectedIcon; + } else if (multiple) { + mergedItemIcon = ; + } else { + mergedItemIcon = null; + } + + let mergedRemoveIcon = null; + if (removeIcon !== undefined) { + mergedRemoveIcon = removeIcon; + } else { + mergedRemoveIcon = ; + } + + return { + clearIcon: mergedClearIcon, + suffixIcon: mergedSuffixIcon, + itemIcon: mergedItemIcon, + removeIcon: mergedRemoveIcon, + }; +} diff --git a/components/tree-select/interface.jsx b/components/tree-select/interface.jsx index e2d988c5c..2524ae37b 100644 --- a/components/tree-select/interface.jsx +++ b/components/tree-select/interface.jsx @@ -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])), diff --git a/components/vc-select2/generate.tsx b/components/vc-select2/generate.tsx index 3d0ae48db..230ec974a 100644 --- a/components/vc-select2/generate.tsx +++ b/components/vc-select2/generate.tsx @@ -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 { 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; } diff --git a/components/vc-select2/index.ts b/components/vc-select2/index.ts index f1a12a1a3..9652b88e2 100644 --- a/components/vc-select2/index.ts +++ b/components/vc-select2/index.ts @@ -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; diff --git a/components/vc-select2/interface/generator.ts b/components/vc-select2/interface/generator.ts index 259f34812..62e4548f5 100644 --- a/components/vc-select2/interface/generator.ts +++ b/components/vc-select2/interface/generator.ts @@ -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 { key?: Key; value?: RawValueType; - label?: Vue.VNodeChild; + label?: VNodeChild; } export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];