import TransBtn from '../TransBtn'; import { LabelValueType, RawValueType, CustomTagProps } from '../interface/generator'; import { RenderNode } from '../interface'; import { InnerSelectorProps } from '.'; import Input from './Input'; import { computed, defineComponent, onMounted, ref, TransitionGroup, VNodeChild, watch, watchEffect, Ref, } from 'vue'; import classNames from '../../_util/classNames'; import pickAttrs from '../../_util/pickAttrs'; import PropTypes from '../../_util/vue-types'; import getTransitionGroupProps from '../../_util/getTransitionGroupProps'; const REST_TAG_KEY = '__RC_SELECT_MAX_REST_COUNT__'; interface SelectorProps extends InnerSelectorProps { // Icon removeIcon?: RenderNode; // Tags maxTagCount?: number; maxTagTextLength?: number; maxTagPlaceholder?: VNodeChild; tokenSeparators?: string[]; tagRender?: (props: CustomTagProps) => VNodeChild; // Motion choiceTransitionName?: string; // Event onSelect: (value: RawValueType, option: { selected: boolean }) => void; } const props = { id: PropTypes.string, prefixCls: PropTypes.string, values: PropTypes.array, open: PropTypes.looseBool, searchValue: PropTypes.string, inputRef: PropTypes.any, placeholder: PropTypes.any, disabled: PropTypes.looseBool, mode: PropTypes.string, showSearch: PropTypes.looseBool, autofocus: PropTypes.looseBool, autocomplete: PropTypes.string, accessibilityIndex: PropTypes.number, tabindex: PropTypes.number, removeIcon: PropTypes.VNodeChild, choiceTransitionName: PropTypes.string, maxTagCount: PropTypes.number, maxTagTextLength: PropTypes.number, maxTagPlaceholder: PropTypes.any.def( (omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`, ), tagRender: PropTypes.func, onSelect: PropTypes.func, onInputChange: PropTypes.func, onInputPaste: PropTypes.func, onInputKeyDown: PropTypes.func, onInputMouseDown: PropTypes.func, onInputCompositionStart: PropTypes.func, onInputCompositionEnd: PropTypes.func, }; const SelectSelector = defineComponent({ name: 'SelectSelector', setup(props) { let motionAppear = false; // not need use ref, because not need trigger watchEffect const measureRef = ref(); const inputWidth = ref(0); // ===================== Motion ====================== onMounted(() => { motionAppear = true; }); // ===================== Search ====================== const inputValue = computed(() => props.open || props.mode === 'tags' ? props.searchValue : '', ); const inputEditable: Ref = computed( () => props.mode === 'tags' || ((props.open && props.showSearch) as boolean), ); // We measure width and set to the input immediately onMounted(() => { watch( inputValue, () => { inputWidth.value = measureRef.value.scrollWidth; }, { flush: 'post' }, ); }); const selectionNode = ref(); watchEffect(() => { const { values, prefixCls, removeIcon, choiceTransitionName, maxTagCount, maxTagTextLength, maxTagPlaceholder = (omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`, tagRender, onSelect, } = props; // ==================== Selection ==================== let displayValues: LabelValueType[] = values; // Cut by `maxTagCount` let restCount: number; if (typeof maxTagCount === 'number') { restCount = values.length - maxTagCount; displayValues = values.slice(0, maxTagCount); } // Update by `maxTagTextLength` if (typeof maxTagTextLength === 'number') { displayValues = displayValues.map(({ label, ...rest }) => { let displayLabel = label; if (typeof label === 'string' || typeof label === 'number') { const strLabel = String(displayLabel); if (strLabel.length > maxTagTextLength) { displayLabel = `${strLabel.slice(0, maxTagTextLength)}...`; } } return { ...rest, label: displayLabel, }; }); } // Fill rest if (restCount > 0) { displayValues.push({ key: REST_TAG_KEY, label: typeof maxTagPlaceholder === 'function' ? maxTagPlaceholder(values.slice(maxTagCount)) : maxTagPlaceholder, }); } const transitionProps = choiceTransitionName ? getTransitionGroupProps(choiceTransitionName, { appear: motionAppear, }) : { css: false }; selectionNode.value = ( {...displayValues.map( ({ key, label, value, disabled: itemDisabled, class: className, style }) => { const mergedKey = key || value; const closable = key !== REST_TAG_KEY && !itemDisabled; const onMousedown = (event: MouseEvent) => { event.preventDefault(); event.stopPropagation(); }; const onClose = (event?: MouseEvent) => { if (event) event.stopPropagation(); onSelect(value as RawValueType, { selected: false }); }; return typeof tagRender === 'function' ? ( {tagRender({ label, value, disabled: itemDisabled, closable, onClose, } as CustomTagProps)} ) : ( {label} {closable && ( × )} ); }, )} ); }); return () => { const { id, prefixCls, values, open, inputRef, placeholder, disabled, autofocus, autocomplete, accessibilityIndex, tabindex, onInputChange, onInputPaste, onInputKeyDown, onInputMouseDown, onInputCompositionStart, onInputCompositionEnd, } = props; return ( <> {selectionNode.value} {/* Measure Node */} {inputValue.value}  {!values.length && !inputValue.value && ( {placeholder} )} ); }; }, }); SelectSelector.inheritAttrs = false; SelectSelector.props = props; export default SelectSelector;