2020-10-07 14:49:01 +00:00
|
|
|
|
/**
|
|
|
|
|
* To match accessibility requirement, we always provide an input in the component.
|
|
|
|
|
* Other element will not set `tabindex` to avoid `onBlur` sequence problem.
|
|
|
|
|
* For focused select, we set `aria-live="polite"` to update the accessibility content.
|
|
|
|
|
*
|
|
|
|
|
* ref:
|
|
|
|
|
* - keyboard: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role#Keyboard_interactions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import KeyCode from '../_util/KeyCode';
|
|
|
|
|
import classNames from '../_util/classNames';
|
|
|
|
|
import Selector from './Selector';
|
|
|
|
|
import SelectTrigger from './SelectTrigger';
|
2021-08-21 08:25:55 +00:00
|
|
|
|
import type { Mode, RenderDOMFunc, OnActiveValue } from './interface';
|
2021-06-26 01:35:40 +00:00
|
|
|
|
import type {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
GetLabeledValue,
|
|
|
|
|
FilterOptions,
|
|
|
|
|
FilterFunc,
|
|
|
|
|
DefaultValueType,
|
|
|
|
|
RawValueType,
|
|
|
|
|
Key,
|
|
|
|
|
DisplayLabelValueType,
|
|
|
|
|
FlattenOptionsType,
|
|
|
|
|
SingleType,
|
|
|
|
|
OnClear,
|
|
|
|
|
SelectSource,
|
|
|
|
|
CustomTagProps,
|
|
|
|
|
} from './interface/generator';
|
2021-06-26 01:35:40 +00:00
|
|
|
|
import { INTERNAL_PROPS_MARK } from './interface/generator';
|
|
|
|
|
import type { OptionListProps } from './OptionList';
|
2020-10-07 14:49:01 +00:00
|
|
|
|
import { toInnerValue, toOuterValues, removeLastEnabledValue, getUUID } from './utils/commonUtil';
|
|
|
|
|
import TransBtn from './TransBtn';
|
|
|
|
|
import useLock from './hooks/useLock';
|
|
|
|
|
import useDelayReset from './hooks/useDelayReset';
|
|
|
|
|
import { getSeparatedContent } from './utils/valueUtil';
|
|
|
|
|
import useSelectTriggerControl from './hooks/useSelectTriggerControl';
|
|
|
|
|
import useCacheDisplayValue from './hooks/useCacheDisplayValue';
|
|
|
|
|
import useCacheOptions from './hooks/useCacheOptions';
|
2021-08-21 08:25:55 +00:00
|
|
|
|
import type { CSSProperties, DefineComponent, PropType, VNode, VNodeChild } from 'vue';
|
2020-10-07 14:49:01 +00:00
|
|
|
|
import {
|
|
|
|
|
computed,
|
|
|
|
|
defineComponent,
|
|
|
|
|
onBeforeUnmount,
|
|
|
|
|
onMounted,
|
|
|
|
|
provide,
|
|
|
|
|
ref,
|
|
|
|
|
watch,
|
2020-10-17 14:19:52 +00:00
|
|
|
|
watchEffect,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
} from 'vue';
|
|
|
|
|
import createRef from '../_util/createRef';
|
2021-08-21 08:25:55 +00:00
|
|
|
|
import PropTypes from '../_util/vue-types';
|
2020-11-17 14:51:00 +00:00
|
|
|
|
import warning from '../_util/warning';
|
2021-06-22 02:47:33 +00:00
|
|
|
|
import isMobile from '../vc-util/isMobile';
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
const DEFAULT_OMIT_PROPS = [
|
|
|
|
|
'children',
|
|
|
|
|
'removeIcon',
|
|
|
|
|
'placeholder',
|
|
|
|
|
'autofocus',
|
|
|
|
|
'maxTagCount',
|
|
|
|
|
'maxTagTextLength',
|
|
|
|
|
'maxTagPlaceholder',
|
|
|
|
|
'choiceTransitionName',
|
|
|
|
|
'onInputKeyDown',
|
2021-06-22 02:47:33 +00:00
|
|
|
|
'tabindex',
|
2020-10-07 14:49:01 +00:00
|
|
|
|
];
|
|
|
|
|
|
2021-08-21 08:25:55 +00:00
|
|
|
|
export function selectBaseProps<OptionType, ValueType>() {
|
|
|
|
|
return {
|
|
|
|
|
prefixCls: String,
|
|
|
|
|
id: String,
|
|
|
|
|
|
|
|
|
|
// Options
|
|
|
|
|
options: { type: Array as PropType<OptionType[]> },
|
|
|
|
|
mode: { type: String as PropType<Mode> },
|
|
|
|
|
|
|
|
|
|
// Value
|
|
|
|
|
value: { type: [String, Number, Object, Array] as PropType<ValueType> },
|
|
|
|
|
defaultValue: { type: [String, Number, Object, Array] as PropType<ValueType> },
|
|
|
|
|
labelInValue: { type: Boolean, default: undefined },
|
|
|
|
|
|
|
|
|
|
// Search
|
|
|
|
|
inputValue: String,
|
|
|
|
|
searchValue: String,
|
|
|
|
|
optionFilterProp: String,
|
|
|
|
|
/**
|
|
|
|
|
* In Select, `false` means do nothing.
|
|
|
|
|
* In TreeSelect, `false` will highlight match item.
|
|
|
|
|
* It's by design.
|
|
|
|
|
*/
|
|
|
|
|
filterOption: {
|
|
|
|
|
type: [Boolean, Function] as PropType<boolean | FilterFunc<OptionType>>,
|
|
|
|
|
default: undefined,
|
|
|
|
|
},
|
|
|
|
|
filterSort: {
|
|
|
|
|
type: Function as PropType<(optionA: OptionType, optionB: OptionType) => number>,
|
|
|
|
|
},
|
|
|
|
|
showSearch: { type: Boolean, default: undefined },
|
|
|
|
|
autoClearSearchValue: { type: Boolean, default: undefined },
|
|
|
|
|
onSearch: { type: Function as PropType<(value: string) => void> },
|
|
|
|
|
onClear: { type: Function as PropType<OnClear> },
|
|
|
|
|
|
|
|
|
|
// Icons
|
|
|
|
|
allowClear: { type: Boolean, default: undefined },
|
|
|
|
|
clearIcon: PropTypes.any,
|
|
|
|
|
showArrow: { type: Boolean, default: undefined },
|
|
|
|
|
inputIcon: PropTypes.VNodeChild,
|
|
|
|
|
removeIcon: PropTypes.VNodeChild,
|
|
|
|
|
menuItemSelectedIcon: PropTypes.VNodeChild,
|
|
|
|
|
|
|
|
|
|
// Dropdown
|
|
|
|
|
open: { type: Boolean, default: undefined },
|
|
|
|
|
defaultOpen: { type: Boolean, default: undefined },
|
|
|
|
|
listHeight: Number,
|
|
|
|
|
listItemHeight: Number,
|
|
|
|
|
dropdownStyle: { type: Function as PropType<CSSProperties> },
|
|
|
|
|
dropdownClassName: String,
|
|
|
|
|
dropdownMatchSelectWidth: {
|
|
|
|
|
type: [Boolean, Number] as PropType<boolean | number>,
|
|
|
|
|
default: undefined,
|
|
|
|
|
},
|
|
|
|
|
virtual: { type: Boolean, default: undefined },
|
|
|
|
|
dropdownRender: { type: Function as PropType<(menu: VNode) => any> },
|
|
|
|
|
dropdownAlign: PropTypes.any,
|
|
|
|
|
animation: String,
|
|
|
|
|
transitionName: String,
|
|
|
|
|
getPopupContainer: { type: Function as PropType<RenderDOMFunc> },
|
|
|
|
|
direction: String,
|
|
|
|
|
|
|
|
|
|
// Others
|
|
|
|
|
disabled: { type: Boolean, default: undefined },
|
|
|
|
|
loading: { type: Boolean, default: undefined },
|
|
|
|
|
autofocus: { type: Boolean, default: undefined },
|
|
|
|
|
defaultActiveFirstOption: { type: Boolean, default: undefined },
|
|
|
|
|
notFoundContent: PropTypes.any,
|
|
|
|
|
placeholder: PropTypes.any,
|
|
|
|
|
backfill: { type: Boolean, default: undefined },
|
|
|
|
|
/** @private Internal usage. Do not use in your production. */
|
|
|
|
|
getInputElement: { type: Function as PropType<() => any> },
|
|
|
|
|
optionLabelProp: String,
|
|
|
|
|
maxTagTextLength: Number,
|
|
|
|
|
maxTagCount: { type: [String, Number] as PropType<number | 'responsive'> },
|
|
|
|
|
maxTagPlaceholder: PropTypes.any,
|
|
|
|
|
tokenSeparators: { type: Array as PropType<string[]> },
|
|
|
|
|
tagRender: { type: Function as PropType<(props: CustomTagProps) => any> },
|
|
|
|
|
showAction: { type: Array as PropType<('focus' | 'click')[]> },
|
|
|
|
|
tabindex: { type: [Number, String] },
|
|
|
|
|
|
|
|
|
|
// Events
|
|
|
|
|
onKeyup: { type: Function as PropType<(e: KeyboardEvent) => void> },
|
|
|
|
|
onKeydown: { type: Function as PropType<(e: KeyboardEvent) => void> },
|
|
|
|
|
onPopupScroll: { type: Function as PropType<(e: UIEvent) => void> },
|
|
|
|
|
onDropdownVisibleChange: { type: Function as PropType<(open: boolean) => void> },
|
|
|
|
|
onSelect: {
|
|
|
|
|
type: Function as PropType<(value: SingleType<ValueType>, option: OptionType) => void>,
|
|
|
|
|
},
|
|
|
|
|
onDeselect: {
|
|
|
|
|
type: Function as PropType<(value: SingleType<ValueType>, option: OptionType) => void>,
|
|
|
|
|
},
|
|
|
|
|
onInputKeyDown: { type: Function as PropType<(e: KeyboardEvent) => void> },
|
|
|
|
|
onClick: { type: Function as PropType<(e: MouseEvent) => void> },
|
|
|
|
|
onChange: {
|
|
|
|
|
type: Function as PropType<(value: ValueType, option: OptionType | OptionType[]) => void>,
|
|
|
|
|
},
|
|
|
|
|
onBlur: { type: Function as PropType<(e: FocusEvent) => void> },
|
|
|
|
|
onFocus: { type: Function as PropType<(e: FocusEvent) => void> },
|
|
|
|
|
onMousedown: { type: Function as PropType<(e: MouseEvent) => void> },
|
|
|
|
|
onMouseenter: { type: Function as PropType<(e: MouseEvent) => void> },
|
|
|
|
|
onMouseleave: { type: Function as PropType<(e: MouseEvent) => void> },
|
|
|
|
|
|
|
|
|
|
// Motion
|
|
|
|
|
choiceTransitionName: String,
|
|
|
|
|
|
|
|
|
|
// Internal props
|
|
|
|
|
/**
|
|
|
|
|
* Only used in current version for internal event process.
|
|
|
|
|
* Do not use in production environment.
|
|
|
|
|
*/
|
|
|
|
|
internalProps: {
|
|
|
|
|
type: Object as PropType<{
|
|
|
|
|
mark?: string;
|
|
|
|
|
onClear?: OnClear;
|
|
|
|
|
skipTriggerChange?: boolean;
|
|
|
|
|
skipTriggerSelect?: boolean;
|
|
|
|
|
onRawSelect?: (value: RawValueType, option: OptionType, source: SelectSource) => void;
|
|
|
|
|
onRawDeselect?: (value: RawValueType, option: OptionType, source: SelectSource) => void;
|
|
|
|
|
}>,
|
|
|
|
|
default: undefined as {
|
|
|
|
|
mark?: string;
|
|
|
|
|
onClear?: OnClear;
|
|
|
|
|
skipTriggerChange?: boolean;
|
|
|
|
|
skipTriggerSelect?: boolean;
|
|
|
|
|
onRawSelect?: (value: RawValueType, option: OptionType, source: SelectSource) => void;
|
|
|
|
|
onRawDeselect?: (value: RawValueType, option: OptionType, source: SelectSource) => void;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
children: Array,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-21 08:25:55 +00:00
|
|
|
|
class Helper<T1, T2> {
|
|
|
|
|
SelectBaseProps = selectBaseProps<T1, T2>();
|
|
|
|
|
}
|
|
|
|
|
type FuncReturnType<T1, T2> = Helper<T1, T2>['SelectBaseProps'];
|
|
|
|
|
|
|
|
|
|
export type SelectProps<T1, T2> = FuncReturnType<T1, T2>;
|
|
|
|
|
|
|
|
|
|
export interface GenerateConfig<OptionType extends object> {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
prefixCls: string;
|
|
|
|
|
components: {
|
2021-08-21 08:25:55 +00:00
|
|
|
|
optionList: DefineComponent<
|
|
|
|
|
Omit<OptionListProps<OptionType>, 'options'> & { options?: OptionType[] }
|
|
|
|
|
>;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
};
|
2021-08-21 08:25:55 +00:00
|
|
|
|
/** Convert jsx tree into `OptionType[]` */
|
|
|
|
|
convertChildrenToData: (children: VNodeChild | JSX.Element) => OptionType[];
|
2020-10-07 14:49:01 +00:00
|
|
|
|
/** Flatten nest options into raw option list */
|
2021-08-21 08:25:55 +00:00
|
|
|
|
flattenOptions: (options: OptionType[], props: any) => FlattenOptionsType<OptionType>;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
/** Convert single raw value into { label, value } format. Will be called by each value */
|
2021-08-21 08:25:55 +00:00
|
|
|
|
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionType>>;
|
|
|
|
|
filterOptions: FilterOptions<OptionType[]>;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
findValueOption: // Need still support legacy ts api
|
2021-08-21 08:25:55 +00:00
|
|
|
|
| ((values: RawValueType[], options: FlattenOptionsType<OptionType>) => OptionType[])
|
2020-10-07 14:49:01 +00:00
|
|
|
|
// New API add prevValueOptions support
|
|
|
|
|
| ((
|
|
|
|
|
values: RawValueType[],
|
2021-08-21 08:25:55 +00:00
|
|
|
|
options: FlattenOptionsType<OptionType>,
|
|
|
|
|
info?: { prevValueOptions?: OptionType[][] },
|
|
|
|
|
) => OptionType[]);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
/** Check if a value is disabled */
|
2021-08-21 08:25:55 +00:00
|
|
|
|
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionType>) => boolean;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
warningProps?: (props: any) => void;
|
|
|
|
|
fillOptionsWithMissingValue?: (
|
2021-08-21 08:25:55 +00:00
|
|
|
|
options: OptionType[],
|
2020-10-07 14:49:01 +00:00
|
|
|
|
value: DefaultValueType,
|
|
|
|
|
optionLabelProp: string,
|
|
|
|
|
labelInValue: boolean,
|
2021-08-21 08:25:55 +00:00
|
|
|
|
) => OptionType[];
|
2020-10-07 14:49:01 +00:00
|
|
|
|
omitDOMProps?: (props: object) => object;
|
|
|
|
|
}
|
|
|
|
|
type ValueType = DefaultValueType;
|
|
|
|
|
/**
|
|
|
|
|
* This function is in internal usage.
|
|
|
|
|
* Do not use it in your prod env since we may refactor this.
|
|
|
|
|
*/
|
|
|
|
|
export default function generateSelector<
|
2021-08-21 08:25:55 +00:00
|
|
|
|
OptionType extends {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
value?: RawValueType;
|
2021-08-21 08:25:55 +00:00
|
|
|
|
label?: any;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
key?: Key;
|
|
|
|
|
disabled?: boolean;
|
2021-08-21 08:25:55 +00:00
|
|
|
|
},
|
|
|
|
|
>(config: GenerateConfig<OptionType>) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const {
|
|
|
|
|
prefixCls: defaultPrefixCls,
|
|
|
|
|
components: { optionList: OptionList },
|
|
|
|
|
convertChildrenToData,
|
|
|
|
|
flattenOptions,
|
|
|
|
|
getLabeledValue,
|
|
|
|
|
filterOptions,
|
|
|
|
|
isValueDisabled,
|
|
|
|
|
findValueOption,
|
|
|
|
|
warningProps,
|
|
|
|
|
fillOptionsWithMissingValue,
|
|
|
|
|
omitDOMProps,
|
2020-10-28 07:31:45 +00:00
|
|
|
|
} = config as any;
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const Select = defineComponent({
|
2020-10-07 14:49:01 +00:00
|
|
|
|
name: 'Select',
|
2021-08-11 07:22:49 +00:00
|
|
|
|
slots: ['option'],
|
2021-08-21 08:25:55 +00:00
|
|
|
|
inheritAttrs: false,
|
|
|
|
|
props: selectBaseProps<OptionType, DefaultValueType>(),
|
|
|
|
|
setup(props) {
|
2020-10-17 14:19:52 +00:00
|
|
|
|
const useInternalProps = computed(
|
|
|
|
|
() => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK,
|
|
|
|
|
);
|
2020-11-17 14:51:00 +00:00
|
|
|
|
warning(
|
|
|
|
|
props.optionFilterProp !== 'children',
|
|
|
|
|
'Select',
|
|
|
|
|
'optionFilterProp not support children, please use label instead',
|
|
|
|
|
);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const containerRef = ref(null);
|
|
|
|
|
const triggerRef = ref(null);
|
|
|
|
|
const selectorRef = ref(null);
|
|
|
|
|
const listRef = ref(null);
|
|
|
|
|
const tokenWithEnter = computed(() =>
|
|
|
|
|
(props.tokenSeparators || []).some(tokenSeparator =>
|
|
|
|
|
['\n', '\r\n'].includes(tokenSeparator),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/** Used for component focused management */
|
|
|
|
|
const [mockFocused, setMockFocused, cancelSetMockFocused] = useDelayReset();
|
|
|
|
|
|
|
|
|
|
const mergedId = computed(() => props.id || `rc_select_${getUUID()}`);
|
|
|
|
|
|
|
|
|
|
// optionLabelProp
|
2020-10-13 09:46:52 +00:00
|
|
|
|
const mergedOptionLabelProp = computed(() => {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
let mergedOptionLabelProp = props.optionLabelProp;
|
|
|
|
|
if (mergedOptionLabelProp === undefined) {
|
|
|
|
|
mergedOptionLabelProp = props.options ? 'label' : 'children';
|
|
|
|
|
}
|
|
|
|
|
return mergedOptionLabelProp;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// labelInValue
|
|
|
|
|
const mergedLabelInValue = computed(() =>
|
|
|
|
|
props.mode === 'combobox' ? false : props.labelInValue,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const isMultiple = computed(() => props.mode === 'tags' || props.mode === 'multiple');
|
|
|
|
|
|
|
|
|
|
const mergedShowSearch = computed(() =>
|
|
|
|
|
props.showSearch !== undefined
|
|
|
|
|
? props.showSearch
|
|
|
|
|
: isMultiple.value || props.mode === 'combobox',
|
|
|
|
|
);
|
|
|
|
|
|
2021-06-22 02:47:33 +00:00
|
|
|
|
const mobile = ref(false);
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
mobile.value = isMobile();
|
|
|
|
|
});
|
|
|
|
|
|
2020-10-07 14:49:01 +00:00
|
|
|
|
// ============================== Ref ===============================
|
|
|
|
|
const selectorDomRef = createRef();
|
|
|
|
|
|
2021-06-22 05:26:19 +00:00
|
|
|
|
const mergedValue = ref();
|
2020-10-07 14:49:01 +00:00
|
|
|
|
watch(
|
2021-06-22 05:26:19 +00:00
|
|
|
|
() => props.value,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
() => {
|
|
|
|
|
mergedValue.value = props.value !== undefined ? props.value : props.defaultValue;
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
);
|
|
|
|
|
// ============================= Value ==============================
|
|
|
|
|
|
|
|
|
|
/** Unique raw values */
|
2021-06-22 02:47:33 +00:00
|
|
|
|
const mergedRawValueArr = computed(() =>
|
2020-10-07 14:49:01 +00:00
|
|
|
|
toInnerValue(mergedValue.value, {
|
|
|
|
|
labelInValue: mergedLabelInValue.value,
|
|
|
|
|
combobox: props.mode === 'combobox',
|
|
|
|
|
}),
|
|
|
|
|
);
|
2021-06-22 02:47:33 +00:00
|
|
|
|
const mergedRawValue = computed(() => mergedRawValueArr.value[0]);
|
|
|
|
|
const mergedValueMap = computed(() => mergedRawValueArr.value[1]);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
/** We cache a set of raw values to speed up check */
|
|
|
|
|
const rawValues = computed(() => new Set(mergedRawValue.value));
|
|
|
|
|
|
|
|
|
|
// ============================= Option =============================
|
|
|
|
|
// Set by option list active, it will merge into search input when mode is `combobox`
|
|
|
|
|
const activeValue = ref(null);
|
|
|
|
|
const setActiveValue = (val: string) => {
|
|
|
|
|
activeValue.value = val;
|
|
|
|
|
};
|
|
|
|
|
const innerSearchValue = ref('');
|
|
|
|
|
const setInnerSearchValue = (val: string) => {
|
|
|
|
|
innerSearchValue.value = val;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mergedSearchValue = computed(() => {
|
|
|
|
|
let mergedSearchValue = innerSearchValue.value;
|
|
|
|
|
if (props.mode === 'combobox' && mergedValue.value !== undefined) {
|
|
|
|
|
mergedSearchValue = mergedValue.value as string;
|
|
|
|
|
} else if (props.searchValue !== undefined) {
|
|
|
|
|
mergedSearchValue = props.searchValue;
|
|
|
|
|
} else if (props.inputValue) {
|
|
|
|
|
mergedSearchValue = props.inputValue;
|
|
|
|
|
}
|
|
|
|
|
return mergedSearchValue;
|
|
|
|
|
});
|
|
|
|
|
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const mergedOptions = computed((): OptionType[] => {
|
2021-06-23 15:08:16 +00:00
|
|
|
|
let newOptions = props.options;
|
|
|
|
|
if (newOptions === undefined) {
|
|
|
|
|
newOptions = convertChildrenToData(props.children);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* `tags` should fill un-list item.
|
|
|
|
|
* This is not cool here since TreeSelect do not need this
|
|
|
|
|
*/
|
|
|
|
|
if (props.mode === 'tags' && fillOptionsWithMissingValue) {
|
|
|
|
|
newOptions = fillOptionsWithMissingValue(
|
|
|
|
|
newOptions,
|
|
|
|
|
mergedValue.value,
|
|
|
|
|
mergedOptionLabelProp.value,
|
|
|
|
|
props.labelInValue,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-21 08:25:55 +00:00
|
|
|
|
return newOptions || ([] as OptionType[]);
|
2021-06-23 15:08:16 +00:00
|
|
|
|
});
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
const mergedFlattenOptions = computed(() => flattenOptions(mergedOptions.value, props));
|
|
|
|
|
|
2021-06-22 02:47:33 +00:00
|
|
|
|
const getValueOption = useCacheOptions(mergedFlattenOptions);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
// Display options for OptionList
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const displayOptions = computed<OptionType[]>(() => {
|
2020-11-07 14:43:31 +00:00
|
|
|
|
if (!mergedSearchValue.value || !mergedShowSearch.value) {
|
2021-08-21 08:25:55 +00:00
|
|
|
|
return [...mergedOptions.value] as OptionType[];
|
2020-10-07 14:49:01 +00:00
|
|
|
|
}
|
|
|
|
|
const { optionFilterProp = 'value', mode, filterOption } = props;
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const filteredOptions: OptionType[] = filterOptions(
|
2020-10-07 14:49:01 +00:00
|
|
|
|
mergedSearchValue.value,
|
|
|
|
|
mergedOptions.value,
|
|
|
|
|
{
|
|
|
|
|
optionFilterProp,
|
|
|
|
|
filterOption:
|
|
|
|
|
mode === 'combobox' && filterOption === undefined ? () => true : filterOption,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
if (
|
|
|
|
|
mode === 'tags' &&
|
|
|
|
|
filteredOptions.every(opt => opt[optionFilterProp] !== mergedSearchValue.value)
|
|
|
|
|
) {
|
|
|
|
|
filteredOptions.unshift({
|
|
|
|
|
value: mergedSearchValue.value,
|
|
|
|
|
label: mergedSearchValue.value,
|
|
|
|
|
key: '__RC_SELECT_TAG_PLACEHOLDER__',
|
2021-08-21 08:25:55 +00:00
|
|
|
|
} as OptionType);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
}
|
2021-06-22 02:47:33 +00:00
|
|
|
|
if (props.filterSort && Array.isArray(filteredOptions)) {
|
2021-08-21 08:25:55 +00:00
|
|
|
|
return ([...filteredOptions] as OptionType[]).sort(props.filterSort);
|
2021-06-22 02:47:33 +00:00
|
|
|
|
}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
return filteredOptions;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const displayFlattenOptions = computed(() => flattenOptions(displayOptions.value, props));
|
2020-10-08 14:51:09 +00:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
watch(
|
|
|
|
|
mergedSearchValue,
|
|
|
|
|
() => {
|
|
|
|
|
if (listRef.value && listRef.value.scrollTo) {
|
|
|
|
|
listRef.value.scrollTo(0);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ flush: 'post', immediate: true },
|
|
|
|
|
);
|
|
|
|
|
});
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
// ============================ Selector ============================
|
|
|
|
|
let displayValues = computed<DisplayLabelValueType[]>(() => {
|
|
|
|
|
const tmpValues = mergedRawValue.value.map((val: RawValueType) => {
|
|
|
|
|
const valueOptions = getValueOption([val]);
|
|
|
|
|
const displayValue = getLabeledValue(val, {
|
|
|
|
|
options: valueOptions,
|
2021-06-22 02:47:33 +00:00
|
|
|
|
prevValueMap: mergedValueMap.value,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
labelInValue: mergedLabelInValue.value,
|
|
|
|
|
optionLabelProp: mergedOptionLabelProp.value,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...displayValue,
|
|
|
|
|
disabled: isValueDisabled(val, valueOptions),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!props.mode &&
|
|
|
|
|
tmpValues.length === 1 &&
|
|
|
|
|
tmpValues[0].value === null &&
|
|
|
|
|
tmpValues[0].label === null
|
|
|
|
|
) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tmpValues;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Polyfill with cache label
|
|
|
|
|
displayValues = useCacheDisplayValue(displayValues);
|
|
|
|
|
|
|
|
|
|
const triggerSelect = (newValue: RawValueType, isSelect: boolean, source: SelectSource) => {
|
|
|
|
|
const newValueOption = getValueOption([newValue]);
|
|
|
|
|
const outOption = findValueOption([newValue], newValueOption)[0];
|
2020-10-17 14:19:52 +00:00
|
|
|
|
const { internalProps = {} } = props;
|
|
|
|
|
if (!internalProps.skipTriggerSelect) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
// Skip trigger `onSelect` or `onDeselect` if configured
|
2021-06-23 15:08:16 +00:00
|
|
|
|
const selectValue = (
|
|
|
|
|
mergedLabelInValue.value
|
|
|
|
|
? getLabeledValue(newValue, {
|
|
|
|
|
options: newValueOption,
|
|
|
|
|
prevValueMap: mergedValueMap.value,
|
|
|
|
|
labelInValue: mergedLabelInValue.value,
|
|
|
|
|
optionLabelProp: mergedOptionLabelProp.value,
|
|
|
|
|
})
|
|
|
|
|
: newValue
|
|
|
|
|
) as SingleType<ValueType>;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
if (isSelect && props.onSelect) {
|
|
|
|
|
props.onSelect(selectValue, outOption);
|
|
|
|
|
} else if (!isSelect && props.onDeselect) {
|
|
|
|
|
props.onDeselect(selectValue, outOption);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trigger internal event
|
|
|
|
|
if (useInternalProps.value) {
|
2020-10-17 14:19:52 +00:00
|
|
|
|
if (isSelect && internalProps.onRawSelect) {
|
|
|
|
|
internalProps.onRawSelect(newValue, outOption, source);
|
|
|
|
|
} else if (!isSelect && internalProps.onRawDeselect) {
|
|
|
|
|
internalProps.onRawDeselect(newValue, outOption, source);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// We need cache options here in case user update the option list
|
|
|
|
|
const prevValueOptions = ref([]);
|
|
|
|
|
const setPrevValueOptions = (val: any[]) => {
|
|
|
|
|
prevValueOptions.value = val;
|
|
|
|
|
};
|
|
|
|
|
const triggerChange = (newRawValues: RawValueType[]) => {
|
2020-10-17 14:19:52 +00:00
|
|
|
|
if (
|
|
|
|
|
useInternalProps.value &&
|
|
|
|
|
props.internalProps &&
|
|
|
|
|
props.internalProps.skipTriggerChange
|
|
|
|
|
) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const newRawValuesOptions = getValueOption(newRawValues);
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const outValues = toOuterValues<FlattenOptionsType<OptionType>>(Array.from(newRawValues), {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
labelInValue: mergedLabelInValue.value,
|
2021-08-21 08:25:55 +00:00
|
|
|
|
options: newRawValuesOptions as any,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
getLabeledValue,
|
2021-06-22 02:47:33 +00:00
|
|
|
|
prevValueMap: mergedValueMap.value,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
optionLabelProp: mergedOptionLabelProp.value,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const outValue: ValueType = (isMultiple.value ? outValues : outValues[0]) as ValueType;
|
|
|
|
|
// Skip trigger if prev & current value is both empty
|
|
|
|
|
if (
|
|
|
|
|
props.onChange &&
|
|
|
|
|
(mergedRawValue.value.length !== 0 || (outValues as []).length !== 0)
|
|
|
|
|
) {
|
|
|
|
|
const outOptions = findValueOption(newRawValues, newRawValuesOptions, {
|
|
|
|
|
prevValueOptions: prevValueOptions.value,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// We will cache option in case it removed by ajax
|
|
|
|
|
setPrevValueOptions(
|
|
|
|
|
outOptions.map((option, index) => {
|
|
|
|
|
const clone = { ...option };
|
|
|
|
|
Object.defineProperty(clone, '_INTERNAL_OPTION_VALUE_', {
|
|
|
|
|
get: () => newRawValues[index],
|
|
|
|
|
});
|
|
|
|
|
return clone;
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
props.onChange(outValue, isMultiple.value ? outOptions : outOptions[0]);
|
|
|
|
|
}
|
|
|
|
|
mergedValue.value = outValue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onInternalSelect = (
|
|
|
|
|
newValue: RawValueType,
|
|
|
|
|
{ selected, source }: { selected: boolean; source: 'option' | 'selection' },
|
|
|
|
|
) => {
|
|
|
|
|
const { autoClearSearchValue = true } = props;
|
|
|
|
|
if (props.disabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let newRawValue: Set<RawValueType>;
|
|
|
|
|
|
|
|
|
|
if (isMultiple.value) {
|
|
|
|
|
newRawValue = new Set(mergedRawValue.value);
|
|
|
|
|
if (selected) {
|
|
|
|
|
newRawValue.add(newValue);
|
|
|
|
|
} else {
|
|
|
|
|
newRawValue.delete(newValue);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
newRawValue = new Set();
|
|
|
|
|
newRawValue.add(newValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Multiple always trigger change and single should change if value changed
|
|
|
|
|
if (
|
|
|
|
|
isMultiple.value ||
|
|
|
|
|
(!isMultiple.value && Array.from(mergedRawValue.value)[0] !== newValue)
|
|
|
|
|
) {
|
|
|
|
|
triggerChange(Array.from(newRawValue));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trigger `onSelect`. Single mode always trigger select
|
|
|
|
|
triggerSelect(newValue, !isMultiple.value || selected, source);
|
|
|
|
|
|
|
|
|
|
// Clean search value if single or configured
|
|
|
|
|
if (props.mode === 'combobox') {
|
|
|
|
|
setInnerSearchValue(String(newValue));
|
|
|
|
|
setActiveValue('');
|
|
|
|
|
} else if (!isMultiple.value || autoClearSearchValue) {
|
|
|
|
|
setInnerSearchValue('');
|
|
|
|
|
setActiveValue('');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onInternalOptionSelect = (newValue: RawValueType, info: { selected: boolean }) => {
|
|
|
|
|
onInternalSelect(newValue, { ...info, source: 'option' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onInternalSelectionSelect = (newValue: RawValueType, info: { selected: boolean }) => {
|
|
|
|
|
onInternalSelect(newValue, { ...info, source: 'selection' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ============================== Open ==============================
|
2020-10-26 14:14:00 +00:00
|
|
|
|
const initOpen = props.open !== undefined ? props.open : props.defaultOpen;
|
|
|
|
|
const innerOpen = ref(initOpen);
|
|
|
|
|
const mergedOpen = ref(initOpen);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const setInnerOpen = (val: boolean) => {
|
2020-10-26 14:14:00 +00:00
|
|
|
|
innerOpen.value = props.open !== undefined ? props.open : val;
|
|
|
|
|
mergedOpen.value = innerOpen.value;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
};
|
|
|
|
|
watch(
|
2020-10-26 14:14:00 +00:00
|
|
|
|
() => props.open,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
() => {
|
2020-10-26 14:14:00 +00:00
|
|
|
|
setInnerOpen(props.open);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Not trigger `open` in `combobox` when `notFoundContent` is empty
|
|
|
|
|
const emptyListContent = computed(
|
|
|
|
|
() => !props.notFoundContent && !displayOptions.value.length,
|
|
|
|
|
);
|
2020-10-17 14:19:52 +00:00
|
|
|
|
|
|
|
|
|
watchEffect(() => {
|
|
|
|
|
mergedOpen.value = innerOpen.value;
|
|
|
|
|
if (
|
|
|
|
|
props.disabled ||
|
|
|
|
|
(emptyListContent.value && mergedOpen.value && props.mode === 'combobox')
|
|
|
|
|
) {
|
|
|
|
|
mergedOpen.value = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
const triggerOpen = computed(() => (emptyListContent.value ? false : mergedOpen.value));
|
|
|
|
|
|
|
|
|
|
const onToggleOpen = (newOpen?: boolean) => {
|
|
|
|
|
const nextOpen = newOpen !== undefined ? newOpen : !mergedOpen.value;
|
|
|
|
|
|
|
|
|
|
if (innerOpen.value !== nextOpen && !props.disabled) {
|
|
|
|
|
setInnerOpen(nextOpen);
|
|
|
|
|
if (props.onDropdownVisibleChange) {
|
|
|
|
|
props.onDropdownVisibleChange(nextOpen);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-10 05:57:37 +00:00
|
|
|
|
useSelectTriggerControl([containerRef, triggerRef], triggerOpen, onToggleOpen);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
// ============================= Search =============================
|
|
|
|
|
const triggerSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => {
|
|
|
|
|
let ret = true;
|
|
|
|
|
let newSearchText = searchText;
|
2020-10-17 14:19:52 +00:00
|
|
|
|
const preSearchValue = mergedSearchValue.value;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
setActiveValue(null);
|
|
|
|
|
|
|
|
|
|
// Check if match the `tokenSeparators`
|
|
|
|
|
const patchLabels: string[] = isCompositing
|
|
|
|
|
? null
|
|
|
|
|
: getSeparatedContent(searchText, props.tokenSeparators);
|
|
|
|
|
let patchRawValues: RawValueType[] = patchLabels;
|
|
|
|
|
|
|
|
|
|
if (props.mode === 'combobox') {
|
|
|
|
|
// Only typing will trigger onChange
|
|
|
|
|
if (fromTyping) {
|
|
|
|
|
triggerChange([newSearchText]);
|
|
|
|
|
}
|
|
|
|
|
} else if (patchLabels) {
|
|
|
|
|
newSearchText = '';
|
|
|
|
|
|
|
|
|
|
if (props.mode !== 'tags') {
|
|
|
|
|
patchRawValues = patchLabels
|
|
|
|
|
.map(label => {
|
|
|
|
|
const item = mergedFlattenOptions.value.find(
|
|
|
|
|
({ data }) => data[mergedOptionLabelProp.value] === label,
|
|
|
|
|
);
|
|
|
|
|
return item ? item.data.value : null;
|
|
|
|
|
})
|
|
|
|
|
.filter((val: RawValueType) => val !== null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newRawValues = Array.from(
|
|
|
|
|
new Set<RawValueType>([...mergedRawValue.value, ...patchRawValues]),
|
|
|
|
|
);
|
|
|
|
|
triggerChange(newRawValues);
|
|
|
|
|
newRawValues.forEach(newRawValue => {
|
|
|
|
|
triggerSelect(newRawValue, true, 'input');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Should close when paste finish
|
|
|
|
|
onToggleOpen(false);
|
|
|
|
|
|
|
|
|
|
// Tell Selector that break next actions
|
|
|
|
|
ret = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setInnerSearchValue(newSearchText);
|
|
|
|
|
|
2020-10-17 14:19:52 +00:00
|
|
|
|
if (props.onSearch && preSearchValue !== newSearchText) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
props.onSearch(newSearchText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Only triggered when menu is closed & mode is tags
|
|
|
|
|
// If menu is open, OptionList will take charge
|
|
|
|
|
// If mode isn't tags, press enter is not meaningful when you can't see any option
|
|
|
|
|
const onSearchSubmit = (searchText: string) => {
|
2021-06-22 02:47:33 +00:00
|
|
|
|
// prevent empty tags from appearing when you click the Enter button
|
|
|
|
|
if (!searchText || !searchText.trim()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const newRawValues = Array.from(
|
|
|
|
|
new Set<RawValueType>([...mergedRawValue.value, searchText]),
|
|
|
|
|
);
|
|
|
|
|
triggerChange(newRawValues);
|
|
|
|
|
newRawValues.forEach(newRawValue => {
|
|
|
|
|
triggerSelect(newRawValue, true, 'input');
|
|
|
|
|
});
|
|
|
|
|
setInnerSearchValue('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Close dropdown when disabled change
|
|
|
|
|
|
|
|
|
|
watch(
|
2021-06-22 05:26:19 +00:00
|
|
|
|
() => props.disabled,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
() => {
|
|
|
|
|
if (innerOpen.value && !!props.disabled) {
|
|
|
|
|
setInnerOpen(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
2020-10-08 14:51:09 +00:00
|
|
|
|
{ immediate: true },
|
2020-10-07 14:49:01 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Close will clean up single mode search text
|
2020-10-08 14:51:09 +00:00
|
|
|
|
watch(
|
|
|
|
|
mergedOpen,
|
|
|
|
|
() => {
|
|
|
|
|
if (!mergedOpen.value && !isMultiple.value && props.mode !== 'combobox') {
|
|
|
|
|
triggerSearch('', false, false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
// ============================ Keyboard ============================
|
|
|
|
|
/**
|
|
|
|
|
* We record input value here to check if can press to clean up by backspace
|
|
|
|
|
* - null: Key is not down, this is reset by key up
|
|
|
|
|
* - true: Search text is empty when first time backspace down
|
|
|
|
|
* - false: Search text is not empty when first time backspace down
|
|
|
|
|
*/
|
|
|
|
|
const [getClearLock, setClearLock] = useLock();
|
|
|
|
|
|
|
|
|
|
// KeyDown
|
|
|
|
|
const onInternalKeyDown = (event: KeyboardEvent) => {
|
|
|
|
|
const clearLock = getClearLock();
|
|
|
|
|
const { which } = event;
|
2021-06-22 02:47:33 +00:00
|
|
|
|
|
|
|
|
|
if (which === KeyCode.ENTER) {
|
|
|
|
|
// Do not submit form when type in the input
|
|
|
|
|
if (props.mode !== 'combobox') {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We only manage open state here, close logic should handle by list component
|
|
|
|
|
if (!mergedOpen.value) {
|
|
|
|
|
onToggleOpen(true);
|
|
|
|
|
}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setClearLock(!!mergedSearchValue.value);
|
|
|
|
|
|
|
|
|
|
// Remove value by `backspace`
|
|
|
|
|
if (
|
|
|
|
|
which === KeyCode.BACKSPACE &&
|
|
|
|
|
!clearLock &&
|
|
|
|
|
isMultiple.value &&
|
|
|
|
|
!mergedSearchValue.value &&
|
|
|
|
|
mergedRawValue.value.length
|
|
|
|
|
) {
|
|
|
|
|
const removeInfo = removeLastEnabledValue(displayValues.value, mergedRawValue.value);
|
|
|
|
|
|
|
|
|
|
if (removeInfo.removedValue !== null) {
|
|
|
|
|
triggerChange(removeInfo.values);
|
|
|
|
|
triggerSelect(removeInfo.removedValue, false, 'input');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mergedOpen.value && listRef.value) {
|
|
|
|
|
listRef.value.onKeydown(event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (props.onKeydown) {
|
|
|
|
|
props.onKeydown(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// KeyUp
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const onInternalKeyUp = (event: KeyboardEvent) => {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
if (mergedOpen.value && listRef.value) {
|
|
|
|
|
listRef.value.onKeyup(event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (props.onKeyup) {
|
|
|
|
|
props.onKeyup(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ========================== Focus / Blur ==========================
|
|
|
|
|
/** Record real focus status */
|
|
|
|
|
const focusRef = ref(false);
|
|
|
|
|
|
|
|
|
|
const onContainerFocus = (...args: any[]) => {
|
|
|
|
|
setMockFocused(true);
|
|
|
|
|
|
|
|
|
|
if (!props.disabled) {
|
|
|
|
|
if (props.onFocus && !focusRef.value) {
|
|
|
|
|
props.onFocus(args[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `showAction` should handle `focus` if set
|
2020-10-17 14:19:52 +00:00
|
|
|
|
if (props.showAction && props.showAction.includes('focus')) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
onToggleOpen(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
focusRef.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onContainerBlur = (...args: any[]) => {
|
|
|
|
|
setMockFocused(false, () => {
|
|
|
|
|
focusRef.value = false;
|
|
|
|
|
onToggleOpen(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (props.disabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-11-03 15:08:43 +00:00
|
|
|
|
const serachVal = mergedSearchValue.value;
|
|
|
|
|
if (serachVal) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
// `tags` mode should move `searchValue` into values
|
|
|
|
|
if (props.mode === 'tags') {
|
|
|
|
|
triggerSearch('', false, false);
|
2020-11-03 15:08:43 +00:00
|
|
|
|
triggerChange(Array.from(new Set([...mergedRawValue.value, serachVal])));
|
2020-10-07 14:49:01 +00:00
|
|
|
|
} else if (props.mode === 'multiple') {
|
|
|
|
|
// `multiple` mode only clean the search value but not trigger event
|
|
|
|
|
setInnerSearchValue('');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (props.onBlur) {
|
|
|
|
|
props.onBlur(args[0]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
provide('VCSelectContainerEvent', {
|
|
|
|
|
focus: onContainerFocus,
|
|
|
|
|
blur: onContainerBlur,
|
|
|
|
|
});
|
|
|
|
|
const activeTimeoutIds: number[] = [];
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
2021-02-25 09:03:19 +00:00
|
|
|
|
activeTimeoutIds.forEach(timeoutId => window.clearTimeout(timeoutId));
|
2020-10-07 14:49:01 +00:00
|
|
|
|
activeTimeoutIds.splice(0, activeTimeoutIds.length);
|
|
|
|
|
});
|
|
|
|
|
onBeforeUnmount(() => {
|
2021-02-25 09:03:19 +00:00
|
|
|
|
activeTimeoutIds.forEach(timeoutId => window.clearTimeout(timeoutId));
|
2020-10-07 14:49:01 +00:00
|
|
|
|
activeTimeoutIds.splice(0, activeTimeoutIds.length);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const onInternalMouseDown = (event: MouseEvent) => {
|
|
|
|
|
const { target } = event;
|
|
|
|
|
const popupElement: HTMLDivElement = triggerRef.value && triggerRef.value.getPopupElement();
|
|
|
|
|
// We should give focus back to selector if clicked item is not focusable
|
|
|
|
|
if (popupElement && popupElement.contains(target as HTMLElement)) {
|
2021-02-25 07:37:09 +00:00
|
|
|
|
const timeoutId = window.setTimeout(() => {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const index = activeTimeoutIds.indexOf(timeoutId);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
activeTimeoutIds.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancelSetMockFocused();
|
|
|
|
|
|
2021-06-22 02:47:33 +00:00
|
|
|
|
if (!mobile.value && !popupElement.contains(document.activeElement)) {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
selectorRef.value.focus();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
activeTimeoutIds.push(timeoutId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (props.onMousedown) {
|
|
|
|
|
props.onMousedown(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ========================= Accessibility ==========================
|
|
|
|
|
const accessibilityIndex = ref(0);
|
|
|
|
|
const mergedDefaultActiveFirstOption = computed(() =>
|
|
|
|
|
props.defaultActiveFirstOption !== undefined
|
|
|
|
|
? props.defaultActiveFirstOption
|
|
|
|
|
: props.mode !== 'combobox',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const onActiveValue: OnActiveValue = (active, index, { source = 'keyboard' } = {}) => {
|
|
|
|
|
accessibilityIndex.value = index;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
props.backfill &&
|
|
|
|
|
props.mode === 'combobox' &&
|
|
|
|
|
active !== null &&
|
|
|
|
|
source === 'keyboard'
|
|
|
|
|
) {
|
|
|
|
|
setActiveValue(String(active));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ============================= Popup ==============================
|
|
|
|
|
const containerWidth = ref(null);
|
2020-10-08 14:51:09 +00:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
watch(
|
|
|
|
|
triggerOpen,
|
|
|
|
|
() => {
|
|
|
|
|
if (triggerOpen.value) {
|
|
|
|
|
const newWidth = Math.ceil(containerRef.value.offsetWidth);
|
2020-11-07 14:43:31 +00:00
|
|
|
|
if (containerWidth.value !== newWidth) {
|
2020-10-08 14:51:09 +00:00
|
|
|
|
containerWidth.value = newWidth;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
);
|
2020-10-07 14:49:01 +00:00
|
|
|
|
});
|
2020-10-08 14:51:09 +00:00
|
|
|
|
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const focus = () => {
|
|
|
|
|
selectorRef.value.focus();
|
|
|
|
|
};
|
|
|
|
|
const blur = () => {
|
|
|
|
|
selectorRef.value.blur();
|
|
|
|
|
};
|
|
|
|
|
return {
|
|
|
|
|
focus,
|
|
|
|
|
blur,
|
2021-06-22 02:47:33 +00:00
|
|
|
|
scrollTo: listRef.value?.scrollTo,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
tokenWithEnter,
|
|
|
|
|
mockFocused,
|
|
|
|
|
mergedId,
|
|
|
|
|
containerWidth,
|
|
|
|
|
onActiveValue,
|
|
|
|
|
accessibilityIndex,
|
|
|
|
|
mergedDefaultActiveFirstOption,
|
|
|
|
|
onInternalMouseDown,
|
|
|
|
|
onContainerFocus,
|
|
|
|
|
onContainerBlur,
|
|
|
|
|
onInternalKeyDown,
|
|
|
|
|
isMultiple,
|
|
|
|
|
mergedOpen,
|
|
|
|
|
displayOptions,
|
|
|
|
|
displayFlattenOptions,
|
|
|
|
|
rawValues,
|
|
|
|
|
onInternalOptionSelect,
|
|
|
|
|
onToggleOpen,
|
|
|
|
|
mergedSearchValue,
|
|
|
|
|
useInternalProps,
|
|
|
|
|
triggerChange,
|
|
|
|
|
triggerSearch,
|
|
|
|
|
mergedRawValue,
|
|
|
|
|
mergedShowSearch,
|
|
|
|
|
onInternalKeyUp,
|
|
|
|
|
triggerOpen,
|
|
|
|
|
mergedOptions,
|
|
|
|
|
onInternalSelectionSelect,
|
|
|
|
|
selectorDomRef,
|
|
|
|
|
displayValues,
|
|
|
|
|
activeValue,
|
|
|
|
|
onSearchSubmit,
|
|
|
|
|
containerRef,
|
|
|
|
|
listRef,
|
2020-10-10 05:57:37 +00:00
|
|
|
|
triggerRef,
|
2020-10-22 10:45:03 +00:00
|
|
|
|
selectorRef,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// We need force update here since popup dom is render async
|
|
|
|
|
onPopupMouseEnter() {
|
2021-08-10 06:36:28 +00:00
|
|
|
|
(this as any).$forceUpdate();
|
2020-10-07 14:49:01 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
render() {
|
|
|
|
|
const {
|
|
|
|
|
tokenWithEnter,
|
|
|
|
|
mockFocused,
|
|
|
|
|
mergedId,
|
|
|
|
|
containerWidth,
|
|
|
|
|
onActiveValue,
|
|
|
|
|
accessibilityIndex,
|
|
|
|
|
mergedDefaultActiveFirstOption,
|
|
|
|
|
onInternalMouseDown,
|
|
|
|
|
onInternalKeyDown,
|
|
|
|
|
isMultiple,
|
|
|
|
|
mergedOpen,
|
|
|
|
|
displayOptions,
|
|
|
|
|
displayFlattenOptions,
|
|
|
|
|
rawValues,
|
|
|
|
|
onInternalOptionSelect,
|
|
|
|
|
onToggleOpen,
|
|
|
|
|
mergedSearchValue,
|
|
|
|
|
onPopupMouseEnter,
|
|
|
|
|
useInternalProps,
|
|
|
|
|
triggerChange,
|
|
|
|
|
triggerSearch,
|
|
|
|
|
mergedRawValue,
|
|
|
|
|
mergedShowSearch,
|
|
|
|
|
onInternalKeyUp,
|
|
|
|
|
triggerOpen,
|
|
|
|
|
mergedOptions,
|
|
|
|
|
onInternalSelectionSelect,
|
|
|
|
|
selectorDomRef,
|
|
|
|
|
displayValues,
|
|
|
|
|
activeValue,
|
|
|
|
|
onSearchSubmit,
|
2021-08-11 07:22:49 +00:00
|
|
|
|
$slots: slots,
|
2020-10-10 05:57:37 +00:00
|
|
|
|
} = this as any;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
const {
|
|
|
|
|
prefixCls = defaultPrefixCls,
|
2020-10-08 14:51:09 +00:00
|
|
|
|
id,
|
|
|
|
|
|
|
|
|
|
open,
|
|
|
|
|
defaultOpen,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
options,
|
2020-10-08 14:51:09 +00:00
|
|
|
|
children,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
mode,
|
2020-10-08 14:51:09 +00:00
|
|
|
|
value,
|
|
|
|
|
defaultValue,
|
|
|
|
|
labelInValue,
|
|
|
|
|
|
|
|
|
|
// Search related
|
|
|
|
|
showSearch,
|
|
|
|
|
inputValue,
|
|
|
|
|
searchValue,
|
|
|
|
|
filterOption,
|
2020-10-19 08:43:10 +00:00
|
|
|
|
optionFilterProp,
|
|
|
|
|
autoClearSearchValue,
|
2020-10-08 14:51:09 +00:00
|
|
|
|
onSearch,
|
|
|
|
|
|
2020-10-07 14:49:01 +00:00
|
|
|
|
// Icons
|
|
|
|
|
allowClear,
|
|
|
|
|
clearIcon,
|
|
|
|
|
showArrow,
|
|
|
|
|
inputIcon,
|
|
|
|
|
menuItemSelectedIcon,
|
|
|
|
|
|
|
|
|
|
// Others
|
|
|
|
|
disabled,
|
|
|
|
|
loading,
|
2020-10-08 14:51:09 +00:00
|
|
|
|
defaultActiveFirstOption,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
notFoundContent = 'Not Found',
|
2020-10-08 14:51:09 +00:00
|
|
|
|
optionLabelProp,
|
|
|
|
|
backfill,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
getInputElement,
|
|
|
|
|
getPopupContainer,
|
|
|
|
|
|
|
|
|
|
// Dropdown
|
|
|
|
|
listHeight = 200,
|
|
|
|
|
listItemHeight = 20,
|
|
|
|
|
animation,
|
|
|
|
|
transitionName,
|
|
|
|
|
virtual,
|
|
|
|
|
dropdownStyle,
|
|
|
|
|
dropdownClassName,
|
|
|
|
|
dropdownMatchSelectWidth,
|
|
|
|
|
dropdownRender,
|
|
|
|
|
dropdownAlign,
|
2020-10-19 08:43:10 +00:00
|
|
|
|
showAction,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
direction,
|
|
|
|
|
|
2020-10-08 14:51:09 +00:00
|
|
|
|
// Tags
|
|
|
|
|
tokenSeparators,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
tagRender,
|
|
|
|
|
|
|
|
|
|
// Events
|
|
|
|
|
onPopupScroll,
|
2020-10-08 14:51:09 +00:00
|
|
|
|
onDropdownVisibleChange,
|
|
|
|
|
onFocus,
|
|
|
|
|
onBlur,
|
|
|
|
|
onKeyup,
|
|
|
|
|
onKeydown,
|
|
|
|
|
onMousedown,
|
|
|
|
|
|
|
|
|
|
onChange,
|
|
|
|
|
onSelect,
|
|
|
|
|
onDeselect,
|
2020-10-07 14:49:01 +00:00
|
|
|
|
onClear,
|
|
|
|
|
|
|
|
|
|
internalProps = {},
|
|
|
|
|
|
|
|
|
|
...restProps
|
2021-08-21 08:25:55 +00:00
|
|
|
|
} = this.$props; //as SelectProps<OptionType[], ValueType>;
|
2020-10-07 14:49:01 +00:00
|
|
|
|
|
|
|
|
|
// ============================= Input ==============================
|
|
|
|
|
// Only works in `combobox`
|
2020-10-08 14:51:09 +00:00
|
|
|
|
const customizeInputElement: VNodeChild | JSX.Element =
|
2020-10-07 14:49:01 +00:00
|
|
|
|
(mode === 'combobox' && getInputElement && getInputElement()) || null;
|
|
|
|
|
|
|
|
|
|
const domProps = omitDOMProps ? omitDOMProps(restProps) : restProps;
|
|
|
|
|
DEFAULT_OMIT_PROPS.forEach(prop => {
|
|
|
|
|
delete domProps[prop];
|
|
|
|
|
});
|
|
|
|
|
const popupNode = (
|
|
|
|
|
<OptionList
|
|
|
|
|
ref="listRef"
|
|
|
|
|
prefixCls={prefixCls}
|
|
|
|
|
id={mergedId}
|
|
|
|
|
open={mergedOpen}
|
|
|
|
|
childrenAsData={!options}
|
|
|
|
|
options={displayOptions}
|
|
|
|
|
flattenOptions={displayFlattenOptions}
|
|
|
|
|
multiple={isMultiple}
|
|
|
|
|
values={rawValues}
|
|
|
|
|
height={listHeight}
|
|
|
|
|
itemHeight={listItemHeight}
|
|
|
|
|
onSelect={onInternalOptionSelect}
|
|
|
|
|
onToggleOpen={onToggleOpen}
|
|
|
|
|
onActiveValue={onActiveValue}
|
|
|
|
|
defaultActiveFirstOption={mergedDefaultActiveFirstOption}
|
|
|
|
|
notFoundContent={notFoundContent}
|
|
|
|
|
onScroll={onPopupScroll}
|
|
|
|
|
searchValue={mergedSearchValue}
|
|
|
|
|
menuItemSelectedIcon={menuItemSelectedIcon}
|
|
|
|
|
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
|
|
|
|
|
onMouseenter={onPopupMouseEnter}
|
2021-08-11 07:22:49 +00:00
|
|
|
|
v-slots={{ option: slots.option }}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// ============================= Clear ==============================
|
|
|
|
|
let clearNode: VNode | JSX.Element;
|
|
|
|
|
const onClearMouseDown = () => {
|
|
|
|
|
// Trigger internal `onClear` event
|
|
|
|
|
if (useInternalProps && internalProps.onClear) {
|
|
|
|
|
internalProps.onClear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (onClear) {
|
|
|
|
|
onClear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
triggerChange([]);
|
|
|
|
|
triggerSearch('', false, false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!disabled && allowClear && (mergedRawValue.length || mergedSearchValue)) {
|
|
|
|
|
clearNode = (
|
|
|
|
|
<TransBtn
|
|
|
|
|
class={`${prefixCls}-clear`}
|
|
|
|
|
onMousedown={onClearMouseDown}
|
|
|
|
|
customizeIcon={clearIcon}
|
|
|
|
|
>
|
|
|
|
|
×
|
|
|
|
|
</TransBtn>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================= Arrow ==============================
|
|
|
|
|
const mergedShowArrow =
|
|
|
|
|
showArrow !== undefined ? showArrow : loading || (!isMultiple && mode !== 'combobox');
|
|
|
|
|
let arrowNode: VNode | JSX.Element;
|
|
|
|
|
|
|
|
|
|
if (mergedShowArrow) {
|
|
|
|
|
arrowNode = (
|
|
|
|
|
<TransBtn
|
|
|
|
|
class={classNames(`${prefixCls}-arrow`, {
|
|
|
|
|
[`${prefixCls}-arrow-loading`]: loading,
|
|
|
|
|
})}
|
|
|
|
|
customizeIcon={inputIcon}
|
|
|
|
|
customizeIconProps={{
|
|
|
|
|
loading,
|
|
|
|
|
searchValue: mergedSearchValue,
|
|
|
|
|
open: mergedOpen,
|
|
|
|
|
focused: mockFocused,
|
|
|
|
|
showSearch: mergedShowSearch,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================ Warning =============================
|
|
|
|
|
if (process.env.NODE_ENV !== 'production' && warningProps) {
|
|
|
|
|
warningProps(this.$props);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================= Render =============================
|
2021-08-21 08:25:55 +00:00
|
|
|
|
const mergedClassName = classNames(prefixCls, this.$attrs.class, {
|
2020-10-07 14:49:01 +00:00
|
|
|
|
[`${prefixCls}-focused`]: mockFocused,
|
|
|
|
|
[`${prefixCls}-multiple`]: isMultiple,
|
|
|
|
|
[`${prefixCls}-single`]: !isMultiple,
|
|
|
|
|
[`${prefixCls}-allow-clear`]: allowClear,
|
|
|
|
|
[`${prefixCls}-show-arrow`]: mergedShowArrow,
|
|
|
|
|
[`${prefixCls}-disabled`]: disabled,
|
|
|
|
|
[`${prefixCls}-loading`]: loading,
|
|
|
|
|
[`${prefixCls}-open`]: mergedOpen,
|
|
|
|
|
[`${prefixCls}-customize-input`]: customizeInputElement,
|
|
|
|
|
[`${prefixCls}-show-search`]: mergedShowSearch,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
2021-08-21 08:25:55 +00:00
|
|
|
|
{...this.$attrs}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
class={mergedClassName}
|
|
|
|
|
{...domProps}
|
|
|
|
|
ref="containerRef"
|
|
|
|
|
onMousedown={onInternalMouseDown}
|
|
|
|
|
onKeydown={onInternalKeyDown}
|
|
|
|
|
onKeyup={onInternalKeyUp}
|
|
|
|
|
// onFocus={onContainerFocus} // trigger by input
|
|
|
|
|
// onBlur={onContainerBlur} // trigger by input
|
|
|
|
|
>
|
|
|
|
|
{mockFocused && !mergedOpen && (
|
|
|
|
|
<span
|
|
|
|
|
style={{
|
|
|
|
|
width: 0,
|
|
|
|
|
height: 0,
|
|
|
|
|
display: 'flex',
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
opacity: 0,
|
|
|
|
|
}}
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
>
|
|
|
|
|
{/* Merge into one string to make screen reader work as expect */}
|
|
|
|
|
{`${mergedRawValue.join(', ')}`}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
<SelectTrigger
|
|
|
|
|
ref="triggerRef"
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
prefixCls={prefixCls}
|
|
|
|
|
visible={triggerOpen}
|
|
|
|
|
popupElement={popupNode}
|
|
|
|
|
containerWidth={containerWidth}
|
|
|
|
|
animation={animation}
|
|
|
|
|
transitionName={transitionName}
|
|
|
|
|
dropdownStyle={dropdownStyle}
|
|
|
|
|
dropdownClassName={dropdownClassName}
|
|
|
|
|
direction={direction}
|
|
|
|
|
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
|
2021-04-09 03:00:41 +00:00
|
|
|
|
dropdownRender={dropdownRender as any}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
dropdownAlign={dropdownAlign}
|
|
|
|
|
getPopupContainer={getPopupContainer}
|
|
|
|
|
empty={!mergedOptions.length}
|
2021-04-09 03:00:41 +00:00
|
|
|
|
getTriggerDOMNode={() => selectorDomRef.current}
|
2020-10-07 14:49:01 +00:00
|
|
|
|
>
|
|
|
|
|
<Selector
|
|
|
|
|
{...this.$props}
|
|
|
|
|
domRef={selectorDomRef}
|
|
|
|
|
prefixCls={prefixCls}
|
|
|
|
|
inputElement={customizeInputElement}
|
|
|
|
|
ref="selectorRef"
|
|
|
|
|
id={mergedId}
|
|
|
|
|
showSearch={mergedShowSearch}
|
|
|
|
|
mode={mode}
|
|
|
|
|
accessibilityIndex={accessibilityIndex}
|
|
|
|
|
multiple={isMultiple}
|
|
|
|
|
tagRender={tagRender}
|
|
|
|
|
values={displayValues}
|
|
|
|
|
open={mergedOpen}
|
|
|
|
|
onToggleOpen={onToggleOpen}
|
|
|
|
|
searchValue={mergedSearchValue}
|
|
|
|
|
activeValue={activeValue}
|
|
|
|
|
onSearch={triggerSearch}
|
|
|
|
|
onSearchSubmit={onSearchSubmit}
|
|
|
|
|
onSelect={onInternalSelectionSelect}
|
|
|
|
|
tokenWithEnter={tokenWithEnter}
|
|
|
|
|
/>
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
|
|
|
|
{arrowNode}
|
|
|
|
|
{clearNode}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return Select;
|
|
|
|
|
}
|