import type { Plugin, ExtractPropTypes, App } from 'vue'; import { computed, defineComponent, ref } from 'vue'; import classNames from '../_util/classNames'; import type { BaseSelectRef } from '../vc-select'; import RcSelect, { selectProps as vcSelectProps, Option, OptGroup } from '../vc-select'; import type { BaseOptionType, DefaultOptionType } from '../vc-select/Select'; import type { OptionProps } from '../vc-select/Option'; import getIcons from './utils/iconUtil'; import PropTypes from '../_util/vue-types'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import { DefaultRenderEmpty } from '../config-provider/renderEmpty'; import omit from '../_util/omit'; import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext'; import type { SelectCommonPlacement } from '../_util/transition'; import { getTransitionDirection, getTransitionName } from '../_util/transition'; import type { SizeType } from '../config-provider'; import { initDefaultProps } from '../_util/props-util'; import type { InputStatus } from '../_util/statusUtils'; import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils'; import { stringType, someType, functionType, booleanType } from '../_util/type'; import { useCompactItemContext } from '../space/Compact'; // CSSINJS import useStyle from './style'; import { useInjectDisabled } from '../config-provider/DisabledContext'; import devWarning from '../vc-util/devWarning'; import type { CustomSlotsType } from '../_util/type'; type RawValue = string | number; export type OptionType = typeof Option; export type { OptionProps, BaseSelectRef as RefSelectProps, BaseOptionType, DefaultOptionType }; export interface LabeledValue { key?: string; value: RawValue; label?: any; } export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[] | undefined; export const selectProps = () => ({ ...omit(vcSelectProps(), [ 'inputIcon', 'mode', 'getInputElement', 'getRawInputElement', 'backfill', ]), value: someType([Array, Object, String, Number]), defaultValue: someType([Array, Object, String, Number]), notFoundContent: PropTypes.any, suffixIcon: PropTypes.any, itemIcon: PropTypes.any, size: stringType(), mode: stringType<'multiple' | 'tags' | 'SECRET_COMBOBOX_MODE_DO_NOT_USE'>(), bordered: booleanType(true), transitionName: String, choiceTransitionName: stringType(''), popupClassName: String, /** @deprecated Please use `popupClassName` instead */ dropdownClassName: String, placement: stringType(), status: stringType(), 'onUpdate:value': functionType<(val: SelectValue) => void>(), }); export type SelectProps = Partial>>; const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE'; const Select = defineComponent({ compatConfig: { MODE: 3 }, name: 'ASelect', Option, OptGroup, inheritAttrs: false, props: initDefaultProps(selectProps(), { listHeight: 256, listItemHeight: 24, }), SECRET_COMBOBOX_MODE_DO_NOT_USE, slots: Object as CustomSlotsType<{ notFoundContent: any; suffixIcon: any; itemIcon: any; removeIcon: any; clearIcon: any; dropdownRender: any; option: any; placeholder: any; tagRender: any; maxTagPlaceholder: any; optionLabel: any; default: any; }>, setup(props, { attrs, emit, slots, expose }) { const selectRef = ref(); const formItemContext = useInjectFormItemContext(); const formItemInputContext = FormItemInputContext.useInject(); const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status)); const focus = () => { selectRef.value?.focus(); }; const blur = () => { selectRef.value?.blur(); }; const scrollTo: BaseSelectRef['scrollTo'] = arg => { selectRef.value?.scrollTo(arg); }; const mode = computed(() => { const { mode } = props; if ((mode as any) === 'combobox') { return undefined; } if (mode === SECRET_COMBOBOX_MODE_DO_NOT_USE) { return 'combobox'; } return mode; }); // ====================== Warning ====================== if (process.env.NODE_ENV !== 'production') { devWarning( !props.dropdownClassName, 'Select', '`dropdownClassName` is deprecated. Please use `popupClassName` instead.', ); } const { prefixCls, direction, configProvider, renderEmpty, size: contextSize, getPrefixCls, getPopupContainer, disabled, select, } = useConfigInject('select', props); const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); const mergedSize = computed(() => compactSize.value || contextSize.value); const contextDisabled = useInjectDisabled(); const mergedDisabled = computed(() => disabled.value ?? contextDisabled.value); // style const [wrapSSR, hashId] = useStyle(prefixCls); const rootPrefixCls = computed(() => getPrefixCls()); // ===================== Placement ===================== const placement = computed(() => { if (props.placement !== undefined) { return props.placement; } return direction.value === 'rtl' ? ('bottomRight' as SelectCommonPlacement) : ('bottomLeft' as SelectCommonPlacement); }); const transitionName = computed(() => getTransitionName( rootPrefixCls.value, getTransitionDirection(placement.value), props.transitionName, ), ); const mergedClassName = computed(() => classNames( { [`${prefixCls.value}-lg`]: mergedSize.value === 'large', [`${prefixCls.value}-sm`]: mergedSize.value === 'small', [`${prefixCls.value}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-borderless`]: !props.bordered, [`${prefixCls.value}-in-form-item`]: formItemInputContext.isFormItemInput, }, getStatusClassNames(prefixCls.value, mergedStatus.value, formItemInputContext.hasFeedback), compactItemClassnames.value, hashId.value, ), ); const triggerChange: SelectProps['onChange'] = (...args) => { emit('update:value', args[0]); emit('change', ...args); formItemContext.onFieldChange(); }; const handleBlur: SelectProps['onBlur'] = e => { emit('blur', e); formItemContext.onFieldBlur(); }; expose({ blur, focus, scrollTo, }); const isMultiple = computed(() => mode.value === 'multiple' || mode.value === 'tags'); const mergedShowArrow = computed(() => props.showArrow !== undefined ? props.showArrow : props.loading || !(isMultiple.value || mode.value === 'combobox'), ); return () => { const { notFoundContent, listHeight = 256, listItemHeight = 24, popupClassName, dropdownClassName, virtual, dropdownMatchSelectWidth, id = formItemContext.id.value, placeholder = slots.placeholder?.(), showArrow, } = props; const { hasFeedback, feedbackIcon } = formItemInputContext; const {} = configProvider; // ===================== Empty ===================== let mergedNotFound: any; if (notFoundContent !== undefined) { mergedNotFound = notFoundContent; } else if (slots.notFoundContent) { mergedNotFound = slots.notFoundContent(); } else if (mode.value === 'combobox') { mergedNotFound = null; } else { mergedNotFound = renderEmpty?.('Select') || ; } // ===================== Icons ===================== const { suffixIcon, itemIcon, removeIcon, clearIcon } = getIcons( { ...props, multiple: isMultiple.value, prefixCls: prefixCls.value, hasFeedback, feedbackIcon, showArrow: mergedShowArrow.value, }, slots, ); const selectProps = omit(props, [ 'prefixCls', 'suffixIcon', 'itemIcon', 'removeIcon', 'clearIcon', 'size', 'bordered', 'status', ]); const rcSelectRtlDropdownClassName = classNames( popupClassName || dropdownClassName, { [`${prefixCls.value}-dropdown-${direction.value}`]: direction.value === 'rtl', }, hashId.value, ); return wrapSSR( , ); }; }, }); /* istanbul ignore next */ Select.install = function (app: App) { app.component(Select.name, Select); app.component(Select.Option.displayName, Select.Option); app.component(Select.OptGroup.displayName, Select.OptGroup); return app; }; export const SelectOption = Select.Option; export const SelectOptGroup = Select.OptGroup; export default Select as typeof Select & Plugin & { readonly Option: typeof Option; readonly OptGroup: typeof OptGroup; readonly SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE'; };