refactor: tree-select

pull/4577/head
tangjinzhou 2021-08-21 16:25:55 +08:00
parent b0678d79ee
commit 956ed09885
12 changed files with 433 additions and 302 deletions

15
components/_util/omit.ts Normal file
View File

@ -0,0 +1,15 @@
function omit<T extends object, K extends [...(keyof T)[]]>(
obj: T,
fields: K,
): {
[K2 in Exclude<keyof T, K[number]>]: T[K2];
} {
// eslint-disable-next-line prefer-object-spread
const shallowCopy = Object.assign({}, obj);
for (let i = 0; i < fields.length; i += 1) {
const key = fields[i];
delete shallowCopy[key];
}
return shallowCopy;
}
export default omit;

View File

@ -1,9 +1,9 @@
import type { VNodeChild, App, PropType, Plugin } from 'vue';
import type { App, PropType, Plugin, ExtractPropTypes } from 'vue';
import { computed, defineComponent, ref } from 'vue';
import omit from 'omit.js';
import classNames from '../_util/classNames';
import type { SelectProps as RcSelectProps } from '../vc-select';
import RcSelect, { Option, OptGroup, BaseProps } from '../vc-select';
import RcSelect, { Option, OptGroup, selectBaseProps } from '../vc-select';
import type { OptionProps as OptionPropsType } from '../vc-select/Option';
import getIcons from './utils/iconUtil';
import PropTypes from '../_util/vue-types';
@ -20,36 +20,26 @@ export type OptionType = typeof Option;
export interface LabeledValue {
key?: string;
value: RawValue;
label: VNodeChild;
label: any;
}
export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[] | undefined;
export interface InternalSelectProps<VT> extends Omit<RcSelectProps<VT>, 'mode'> {
suffixIcon?: VNodeChild;
itemIcon?: VNodeChild;
interface InternalSelectProps<VT> extends Omit<RcSelectProps<VT>, 'mode'> {
suffixIcon?: any;
itemIcon?: any;
size?: SizeType;
mode?: 'multiple' | 'tags' | 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
bordered?: boolean;
}
export interface SelectPropsTypes<VT>
extends Omit<
InternalSelectProps<VT>,
'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style'
> {
interface SelectPropsTypes<VT>
extends Omit<InternalSelectProps<VT>, 'inputIcon' | 'mode' | 'getInputElement' | 'backfill'> {
mode?: 'multiple' | 'tags';
}
export type SelectTypes = SelectPropsTypes<SelectValue>;
export const SelectProps = () => ({
...(omit(BaseProps(), [
'inputIcon',
'mode',
'getInputElement',
'backfill',
'class',
'style',
]) as Omit<
ReturnType<typeof BaseProps>,
export type SelectProps = Partial<ExtractPropTypes<SelectPropsTypes<SelectValue>>>;
export const selectProps = () => ({
...(omit(selectBaseProps(), ['inputIcon', 'mode', 'getInputElement', 'backfill']) as Omit<
SelectPropsTypes<SelectValue>,
'inputIcon' | 'mode' | 'getInputElement' | 'backfill' | 'class' | 'style'
>),
value: {
@ -58,9 +48,9 @@ export const SelectProps = () => ({
defaultValue: {
type: [Array, Object, String, Number] as PropType<SelectValue>,
},
notFoundContent: PropTypes.VNodeChild,
suffixIcon: PropTypes.VNodeChild,
itemIcon: PropTypes.VNodeChild,
notFoundContent: PropTypes.any,
suffixIcon: PropTypes.any,
itemIcon: PropTypes.any,
size: PropTypes.oneOf(tuple('small', 'middle', 'large', 'default')),
mode: PropTypes.oneOf(tuple('multiple', 'tags', 'SECRET_COMBOBOX_MODE_DO_NOT_USE')),
bordered: PropTypes.looseBool.def(true),
@ -73,7 +63,7 @@ const Select = defineComponent({
Option,
OptGroup,
inheritAttrs: false,
props: SelectProps(),
props: selectProps(),
SECRET_COMBOBOX_MODE_DO_NOT_USE: 'SECRET_COMBOBOX_MODE_DO_NOT_USE',
emits: ['change', 'update:value'],
slots: [
@ -146,7 +136,7 @@ const Select = defineComponent({
const isMultiple = mode.value === 'multiple' || mode.value === 'tags';
// ===================== Empty =====================
let mergedNotFound: VNodeChild;
let mergedNotFound: any;
if (notFoundContent !== undefined) {
mergedNotFound = notFoundContent;
} else if (slots.notFoundContent) {

View File

@ -5,7 +5,7 @@ import classNames from '../_util/classNames';
import pickAttrs from '../_util/pickAttrs';
import { isValidElement } from '../_util/props-util';
import createRef from '../_util/createRef';
import type { PropType, VNodeChild } from 'vue';
import type { PropType } from 'vue';
import { computed, defineComponent, nextTick, reactive, watch } from 'vue';
import List from '../vc-virtual-list/List';
import type {
@ -22,18 +22,18 @@ export interface RefOptionListProps {
onKeyup: (e?: KeyboardEvent) => void;
scrollTo?: (index: number) => void;
}
export interface OptionListProps {
export interface OptionListProps<OptionType extends object> {
prefixCls: string;
id: string;
options: SelectOptionsType;
flattenOptions: FlattenOptionsType<SelectOptionsType>;
options: OptionType[];
flattenOptions: FlattenOptionsType<OptionType>;
height: number;
itemHeight: number;
values: Set<RawValueType>;
multiple: boolean;
open: boolean;
defaultActiveFirstOption?: boolean;
notFoundContent?: VNodeChild;
notFoundContent?: any;
menuItemSelectedIcon?: RenderNode;
childrenAsData: boolean;
searchValue: string;
@ -80,7 +80,7 @@ const OptionListProps = {
* Using virtual list of option display.
* Will fallback to dom if use customize render.
*/
const OptionList = defineComponent<OptionListProps, { state?: any }>({
const OptionList = defineComponent<OptionListProps<SelectOptionsType[number]>, { state?: any }>({
name: 'OptionList',
inheritAttrs: false,
slots: ['option'],
@ -290,7 +290,7 @@ const OptionList = defineComponent<OptionListProps, { state?: any }>({
virtual,
onScroll,
onMouseenter,
} = this.$props as OptionListProps;
} = this.$props;
const renderOption = $slots.option;
const { activeIndex } = this.state;
// ========================== Render ==========================

View File

@ -47,9 +47,8 @@ import generateSelector from './generate';
import type { DefaultValueType } from './interface/generator';
import warningProps from './utils/warningPropsUtil';
import { defineComponent, ref } from 'vue';
import omit from 'lodash-es/omit';
const RefSelect = generateSelector<SelectOptionsType>({
const RefSelect = generateSelector<SelectOptionsType[number]>({
prefixCls: 'rc-select',
components: {
optionList: SelectOptionList as any,
@ -64,10 +63,17 @@ const RefSelect = generateSelector<SelectOptionsType>({
fillOptionsWithMissingValue,
});
export type ExportedSelectProps<ValueType extends DefaultValueType = DefaultValueType> =
SelectProps<SelectOptionsType, ValueType>;
export type ExportedSelectProps<T extends DefaultValueType = DefaultValueType> = SelectProps<
SelectOptionsType[number],
T
>;
const Select = defineComponent<Omit<ExportedSelectProps, 'children'>>({
const Select = defineComponent({
name: 'Select',
inheritAttrs: false,
Option: Option,
OptGroup: OptGroup,
props: RefSelect.props,
setup(props, { attrs, expose, slots }) {
const selectRef = ref(null);
expose({
@ -91,8 +97,4 @@ const Select = defineComponent<Omit<ExportedSelectProps, 'children'>>({
};
},
});
Select.inheritAttrs = false;
Select.props = omit(RefSelect.props, ['children']);
Select.Option = Option;
Select.OptGroup = OptGroup;
export default Select;

View File

@ -57,7 +57,7 @@ export interface SelectorProps {
onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
onSearchSubmit: (searchText: string) => void;
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
onInputKeyDown?: EventHandlerNonNull;
onInputKeyDown?: (e: KeyboardEvent) => void;
/**
* @private get real dom for trigger align.

View File

@ -11,14 +11,13 @@ import KeyCode from '../_util/KeyCode';
import classNames from '../_util/classNames';
import Selector from './Selector';
import SelectTrigger from './SelectTrigger';
import type { RenderNode, Mode, RenderDOMFunc, OnActiveValue } from './interface';
import type { Mode, RenderDOMFunc, OnActiveValue } from './interface';
import type {
GetLabeledValue,
FilterOptions,
FilterFunc,
DefaultValueType,
RawValueType,
LabelValueType,
Key,
DisplayLabelValueType,
FlattenOptionsType,
@ -26,7 +25,6 @@ import type {
OnClear,
SelectSource,
CustomTagProps,
DropdownRender,
} from './interface/generator';
import { INTERNAL_PROPS_MARK } from './interface/generator';
import type { OptionListProps } from './OptionList';
@ -38,7 +36,7 @@ import { getSeparatedContent } from './utils/valueUtil';
import useSelectTriggerControl from './hooks/useSelectTriggerControl';
import useCacheDisplayValue from './hooks/useCacheDisplayValue';
import useCacheOptions from './hooks/useCacheOptions';
import type { CSSProperties, DefineComponent, VNode, VNodeChild } from 'vue';
import type { CSSProperties, DefineComponent, PropType, VNode, VNodeChild } from 'vue';
import {
computed,
defineComponent,
@ -50,8 +48,7 @@ import {
watchEffect,
} from 'vue';
import createRef from '../_util/createRef';
import PropTypes, { withUndefined } from '../_util/vue-types';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import PropTypes from '../_util/vue-types';
import warning from '../_util/warning';
import isMobile from '../vc-util/isMobile';
@ -68,248 +65,177 @@ const DEFAULT_OMIT_PROPS = [
'tabindex',
];
export const BaseProps = () => ({
prefixCls: PropTypes.string,
id: PropTypes.string,
class: PropTypes.string,
style: PropTypes.any,
export function selectBaseProps<OptionType, ValueType>() {
return {
prefixCls: String,
id: String,
// Options
options: PropTypes.array,
mode: PropTypes.string,
// Options
options: { type: Array as PropType<OptionType[]> },
mode: { type: String as PropType<Mode> },
// Value
value: PropTypes.any,
defaultValue: PropTypes.any,
labelInValue: PropTypes.looseBool,
// 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: PropTypes.string,
searchValue: PropTypes.string,
optionFilterProp: PropTypes.string,
/**
* In Select, `false` means do nothing.
* In TreeSelect, `false` will highlight match item.
* It's by design.
*/
filterOption: PropTypes.any,
filterSort: PropTypes.func,
showSearch: PropTypes.looseBool,
autoClearSearchValue: PropTypes.looseBool,
onSearch: PropTypes.func,
onClear: PropTypes.func,
// 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: PropTypes.looseBool,
clearIcon: PropTypes.VNodeChild,
showArrow: PropTypes.looseBool,
inputIcon: PropTypes.VNodeChild,
removeIcon: PropTypes.VNodeChild,
menuItemSelectedIcon: PropTypes.VNodeChild,
// 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: PropTypes.looseBool,
defaultOpen: PropTypes.looseBool,
listHeight: PropTypes.number,
listItemHeight: PropTypes.number,
dropdownStyle: PropTypes.object,
dropdownClassName: PropTypes.string,
dropdownMatchSelectWidth: withUndefined(PropTypes.oneOfType([Boolean, Number])),
virtual: PropTypes.looseBool,
dropdownRender: PropTypes.func,
dropdownAlign: PropTypes.any,
animation: PropTypes.string,
transitionName: PropTypes.string,
getPopupContainer: PropTypes.func,
direction: PropTypes.string,
// 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: PropTypes.looseBool,
loading: PropTypes.looseBool,
autofocus: PropTypes.looseBool,
defaultActiveFirstOption: PropTypes.looseBool,
notFoundContent: PropTypes.VNodeChild,
placeholder: PropTypes.VNodeChild,
backfill: PropTypes.looseBool,
getInputElement: PropTypes.func,
optionLabelProp: PropTypes.string,
maxTagTextLength: PropTypes.number,
maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
maxTagPlaceholder: PropTypes.any,
tokenSeparators: PropTypes.array,
tagRender: PropTypes.func,
showAction: PropTypes.array,
tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.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: 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,
// 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: PropTypes.string,
// Motion
choiceTransitionName: String,
// Internal props
/**
* Only used in current version for internal event process.
* Do not use in production environment.
*/
internalProps: PropTypes.object,
children: PropTypes.array,
});
export interface SelectProps<OptionsType extends object[], ValueType> {
prefixCls?: string;
id?: string;
class?: string;
style?: CSSProperties;
// Options
options?: OptionsType;
children?: any[];
mode?: Mode;
// Value
value?: ValueType;
defaultValue?: ValueType;
labelInValue?: boolean;
// 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?: boolean | FilterFunc<OptionsType[number]>;
filterSort?: (optionA: OptionsType[number], optionB: OptionsType[number]) => number;
showSearch?: boolean;
autoClearSearchValue?: boolean;
onSearch?: (value: string) => void;
onClear?: OnClear;
// Icons
allowClear?: boolean;
clearIcon?: VNodeChild;
showArrow?: boolean;
inputIcon?: RenderNode;
removeIcon?: VNodeChild;
menuItemSelectedIcon?: RenderNode;
// Dropdown
open?: boolean;
defaultOpen?: boolean;
listHeight?: number;
listItemHeight?: number;
dropdownStyle?: CSSProperties;
dropdownClassName?: string;
dropdownMatchSelectWidth?: boolean | number;
virtual?: boolean;
dropdownRender?: DropdownRender;
dropdownAlign?: any;
animation?: string;
transitionName?: string;
getPopupContainer?: RenderDOMFunc;
direction?: string;
// Others
disabled?: boolean;
loading?: boolean;
autofocus?: boolean;
defaultActiveFirstOption?: boolean;
notFoundContent?: VNodeChild;
placeholder?: VNodeChild;
backfill?: boolean;
getInputElement?: () => VNodeChild | JSX.Element;
optionLabelProp?: string;
maxTagTextLength?: number;
maxTagCount?: number | 'responsive';
maxTagPlaceholder?: VNodeChild | ((omittedValues: LabelValueType[]) => VNodeChild);
tokenSeparators?: string[];
tagRender?: (props: CustomTagProps) => VNodeChild;
showAction?: ('focus' | 'click')[];
tabindex?: number | string;
// Events
onKeyup?: EventHandlerNonNull;
onKeydown?: EventHandlerNonNull;
onPopupScroll?: EventHandlerNonNull;
onDropdownVisibleChange?: (open: boolean) => void;
onSelect?: (value: SingleType<ValueType>, option: OptionsType[number]) => void;
onDeselect?: (value: SingleType<ValueType>, option: OptionsType[number]) => void;
onInputKeyDown?: EventHandlerNonNull;
onClick?: EventHandlerNonNull;
onChange?: (value: ValueType, option: OptionsType[number] | OptionsType) => void;
onBlur?: EventHandlerNonNull;
onFocus?: EventHandlerNonNull;
onMousedown?: EventHandlerNonNull;
onMouseenter?: EventHandlerNonNull;
onMouseleave?: EventHandlerNonNull;
// Motion
choiceTransitionName?: string;
// Internal props
/**
* Only used in current version for internal event process.
* Do not use in production environment.
*/
internalProps?: {
mark?: string;
onClear?: OnClear;
skipTriggerChange?: boolean;
skipTriggerSelect?: boolean;
onRawSelect?: (value: RawValueType, option: OptionsType[number], source: SelectSource) => void;
onRawDeselect?: (
value: RawValueType,
option: OptionsType[number],
source: SelectSource,
) => void;
// 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,
};
}
export interface GenerateConfig<OptionsType extends object[]> {
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> {
prefixCls: string;
components: {
optionList: DefineComponent<Omit<OptionListProps, 'options'> & { options?: OptionsType }>;
optionList: DefineComponent<
Omit<OptionListProps<OptionType>, 'options'> & { options?: OptionType[] }
>;
};
/** Convert jsx tree into `OptionsType` */
convertChildrenToData: (children: VNodeChild | JSX.Element) => OptionsType;
/** Convert jsx tree into `OptionType[]` */
convertChildrenToData: (children: VNodeChild | JSX.Element) => OptionType[];
/** Flatten nest options into raw option list */
flattenOptions: (options: OptionsType, props: any) => FlattenOptionsType<OptionsType>;
flattenOptions: (options: OptionType[], props: any) => FlattenOptionsType<OptionType>;
/** Convert single raw value into { label, value } format. Will be called by each value */
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionsType>>;
filterOptions: FilterOptions<OptionsType>;
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionType>>;
filterOptions: FilterOptions<OptionType[]>;
findValueOption: // Need still support legacy ts api
| ((values: RawValueType[], options: FlattenOptionsType<OptionsType>) => OptionsType)
| ((values: RawValueType[], options: FlattenOptionsType<OptionType>) => OptionType[])
// New API add prevValueOptions support
| ((
values: RawValueType[],
options: FlattenOptionsType<OptionsType>,
info?: { prevValueOptions?: OptionsType[] },
) => OptionsType);
options: FlattenOptionsType<OptionType>,
info?: { prevValueOptions?: OptionType[][] },
) => OptionType[]);
/** Check if a value is disabled */
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionsType>) => boolean;
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionType>) => boolean;
warningProps?: (props: any) => void;
fillOptionsWithMissingValue?: (
options: OptionsType,
options: OptionType[],
value: DefaultValueType,
optionLabelProp: string,
labelInValue: boolean,
) => OptionsType;
) => OptionType[];
omitDOMProps?: (props: object) => object;
}
type ValueType = DefaultValueType;
@ -318,13 +244,13 @@ type ValueType = DefaultValueType;
* Do not use it in your prod env since we may refactor this.
*/
export default function generateSelector<
OptionsType extends {
OptionType extends {
value?: RawValueType;
label?: VNodeChild;
label?: any;
key?: Key;
disabled?: boolean;
}[],
>(config: GenerateConfig<OptionsType>) {
},
>(config: GenerateConfig<OptionType>) {
const {
prefixCls: defaultPrefixCls,
components: { optionList: OptionList },
@ -338,10 +264,12 @@ export default function generateSelector<
fillOptionsWithMissingValue,
omitDOMProps,
} = config as any;
const Select = defineComponent<SelectProps<OptionsType, ValueType>>({
const Select = defineComponent({
name: 'Select',
slots: ['option'],
setup(props: SelectProps<OptionsType, ValueType>) {
inheritAttrs: false,
props: selectBaseProps<OptionType, DefaultValueType>(),
setup(props) {
const useInternalProps = computed(
() => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK,
);
@ -440,7 +368,7 @@ export default function generateSelector<
return mergedSearchValue;
});
const mergedOptions = computed((): OptionsType => {
const mergedOptions = computed((): OptionType[] => {
let newOptions = props.options;
if (newOptions === undefined) {
newOptions = convertChildrenToData(props.children);
@ -459,7 +387,7 @@ export default function generateSelector<
);
}
return newOptions || ([] as OptionsType);
return newOptions || ([] as OptionType[]);
});
const mergedFlattenOptions = computed(() => flattenOptions(mergedOptions.value, props));
@ -467,12 +395,12 @@ export default function generateSelector<
const getValueOption = useCacheOptions(mergedFlattenOptions);
// Display options for OptionList
const displayOptions = computed<OptionsType>(() => {
const displayOptions = computed<OptionType[]>(() => {
if (!mergedSearchValue.value || !mergedShowSearch.value) {
return [...mergedOptions.value] as OptionsType;
return [...mergedOptions.value] as OptionType[];
}
const { optionFilterProp = 'value', mode, filterOption } = props;
const filteredOptions: OptionsType = filterOptions(
const filteredOptions: OptionType[] = filterOptions(
mergedSearchValue.value,
mergedOptions.value,
{
@ -489,10 +417,10 @@ export default function generateSelector<
value: mergedSearchValue.value,
label: mergedSearchValue.value,
key: '__RC_SELECT_TAG_PLACEHOLDER__',
});
} as OptionType);
}
if (props.filterSort && Array.isArray(filteredOptions)) {
return ([...filteredOptions] as OptionsType).sort(props.filterSort);
return ([...filteredOptions] as OptionType[]).sort(props.filterSort);
}
return filteredOptions;
@ -591,9 +519,9 @@ export default function generateSelector<
return;
}
const newRawValuesOptions = getValueOption(newRawValues);
const outValues = toOuterValues<FlattenOptionsType<OptionsType>>(Array.from(newRawValues), {
const outValues = toOuterValues<FlattenOptionsType<OptionType>>(Array.from(newRawValues), {
labelInValue: mergedLabelInValue.value,
options: newRawValuesOptions,
options: newRawValuesOptions as any,
getLabeledValue,
prevValueMap: mergedValueMap.value,
optionLabelProp: mergedOptionLabelProp.value,
@ -872,7 +800,7 @@ export default function generateSelector<
};
// KeyUp
const onInternalKeyUp = (event: Event) => {
const onInternalKeyUp = (event: KeyboardEvent) => {
if (mergedOpen.value && listRef.value) {
listRef.value.onKeyup(event);
}
@ -1098,7 +1026,6 @@ export default function generateSelector<
} = this as any;
const {
prefixCls = defaultPrefixCls,
class: className,
id,
open,
@ -1172,7 +1099,7 @@ export default function generateSelector<
internalProps = {},
...restProps
} = this.$props as SelectProps<OptionsType, ValueType>;
} = this.$props; //as SelectProps<OptionType[], ValueType>;
// ============================= Input ==============================
// Only works in `combobox`
@ -1267,7 +1194,7 @@ export default function generateSelector<
}
// ============================= Render =============================
const mergedClassName = classNames(prefixCls, className, {
const mergedClassName = classNames(prefixCls, this.$attrs.class, {
[`${prefixCls}-focused`]: mockFocused,
[`${prefixCls}-multiple`]: isMultiple,
[`${prefixCls}-single`]: !isMultiple,
@ -1282,6 +1209,7 @@ export default function generateSelector<
return (
<div
{...this.$attrs}
class={mergedClassName}
{...domProps}
ref="containerRef"
@ -1355,6 +1283,5 @@ export default function generateSelector<
);
},
});
Select.props = initDefaultProps(BaseProps(), {});
return Select;
}

View File

@ -1,17 +1,17 @@
import type { Ref, VNodeChild } from 'vue';
import type { Ref } from 'vue';
import { computed } from 'vue';
import type { RawValueType, FlattenOptionsType, Key } from '../interface/generator';
export default function useCacheOptions<
OptionsType extends {
OptionType extends {
value?: RawValueType;
label?: VNodeChild;
label?: any;
key?: Key;
disabled?: boolean;
}[],
},
>(options: Ref) {
const optionMap = computed(() => {
const map: Map<RawValueType, FlattenOptionsType<OptionsType>[number]> = new Map();
const map: Map<RawValueType, FlattenOptionsType<OptionType>[number]> = new Map();
options.value.forEach((item: any) => {
const {
data: { value },
@ -21,7 +21,7 @@ export default function useCacheOptions<
return map;
});
const getValueOption = (vals: RawValueType[]): FlattenOptionsType<OptionsType> =>
const getValueOption = (vals: RawValueType[]) =>
vals.map(value => optionMap.value.get(value)).filter(Boolean);
return getValueOption;

View File

@ -2,9 +2,9 @@ import type { ExportedSelectProps } from './Select';
import Select from './Select';
import Option from './Option';
import OptGroup from './OptGroup';
import { BaseProps } from './generate';
import { selectBaseProps } from './generate';
export type SelectProps<T = any> = ExportedSelectProps<T>;
export { Option, OptGroup, BaseProps };
export { Option, OptGroup, selectBaseProps };
export default Select;

View File

@ -56,9 +56,11 @@ export type FilterOptions<OptionsType extends object[]> = (
export type FilterFunc<OptionType> = (inputValue: string, option?: OptionType) => boolean;
export type FlattenOptionsType<OptionsType extends object[] = object[]> = {
export type FlattenOptionsType<OptionType = object> = {
key: Key;
data: OptionsType[number];
data: OptionType;
label?: any;
value?: RawValueType;
/** Used for customize data */
[name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}[];

View File

@ -1,6 +1,100 @@
import { GenerateConfig } from '../vc-select/generate';
import generateSelector, { GenerateConfig } from '../vc-select/generate';
import TreeNode from './TreeNode';
import type {
Key,
DefaultValueType,
DataNode,
LabelValueType,
SimpleModeConfig,
RawValueType,
ChangeEventExtra,
LegacyDataNode,
SelectSource,
FlattenDataNode,
FieldNames,
} from './interface';
import {
flattenOptions,
filterOptions,
isValueDisabled,
findValueOption,
addValue,
removeValue,
getRawValueLabeled,
toArray,
fillFieldNames,
} from './utils/valueUtil';
import warningProps from './utils/warningPropsUtil';
import { SelectContext } from './Context';
import useTreeData from './hooks/useTreeData';
import useKeyValueMap from './hooks/useKeyValueMap';
import useKeyValueMapping from './hooks/useKeyValueMapping';
import type { CheckedStrategy } from './utils/strategyUtil';
import { formatStrategyKeys, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './utils/strategyUtil';
import { fillAdditionalInfo } from './utils/legacyUtil';
import useSelectValues from './hooks/useSelectValues';
import { treeSelectProps, TreeSelectProps } from './props';
import { getLabeledValue } from '../vc-select/utils/valueUtil';
import omit from '../_util/omit';
import { defineComponent } from 'vue';
const OMIT_PROPS: (keyof TreeSelectProps)[] = [
'expandedKeys' as any,
'treeData',
'treeCheckable',
'showCheckedStrategy',
'searchPlaceholder',
'treeLine',
'treeIcon',
'showTreeIcon',
'switcherIcon',
'treeNodeFilterProp',
'filterTreeNode',
'dropdownPopupAlign',
'treeDefaultExpandAll',
'treeCheckStrictly',
'treeExpandedKeys',
'treeLoadedKeys',
'treeMotion',
'onTreeExpand',
'onTreeLoad',
'labelRender',
'loadData',
'treeDataSimpleMode',
'treeNodeLabelProp',
'treeDefaultExpandedKeys',
];
export default function generate(config: {
prefixCls: string;
optionList: GenerateConfig<any>['components']['optionList'];
}) {}
optionList: GenerateConfig<DataNode>['components']['optionList'];
}) {
const { prefixCls, optionList } = config;
const RefSelect = generateSelector<DataNode>({
prefixCls,
components: {
optionList,
},
// Not use generate since we will handle ourself
convertChildrenToData: () => null,
flattenOptions,
// Handle `optionLabelProp` in TreeSelect component
getLabeledValue: getLabeledValue as any,
filterOptions,
isValueDisabled,
findValueOption,
omitDOMProps: (props: TreeSelectProps<any>) => omit(props, OMIT_PROPS),
});
return defineComponent({
props: treeSelectProps(),
slots: [],
name: 'TreeSelect',
setup(props) {
return () => {
return null;
};
},
});
}

View File

@ -1,7 +1,21 @@
import type { ExtractPropTypes, PropType } from 'vue';
import type { DataNode } from '../tree';
import { selectBaseProps } from '../vc-select';
import type { FilterFunc } from '../vc-select/interface/generator';
import omit from '../_util/omit';
import type { Key } from '../_util/type';
import PropTypes from '../_util/vue-types';
import type { FlattenDataNode, RawValueType } from './interface';
import type {
ChangeEventExtra,
DefaultValueType,
FieldNames,
FlattenDataNode,
LabelValueType,
LegacyDataNode,
RawValueType,
SimpleModeConfig,
} from './interface';
import type { CheckedStrategy } from './utils/strategyUtil';
export function optionListProps<OptionsType>() {
return {
@ -33,6 +47,94 @@ export function optionListProps<OptionsType>() {
};
}
export type OptionListProps = Partial<
Omit<ExtractPropTypes<ReturnType<typeof optionListProps>>, 'options'> & { options: DataNode[] }
export function treeSelectProps<ValueType = DefaultValueType>() {
const selectProps = omit(selectBaseProps<DataNode, ValueType>(), [
'onChange',
'mode',
'menuItemSelectedIcon',
'dropdownAlign',
'backfill',
'getInputElement',
'optionLabelProp',
'tokenSeparators',
'filterOption',
]);
return {
...selectProps,
multiple: { type: Boolean, default: undefined },
showArrow: { type: Boolean, default: undefined },
showSearch: { type: Boolean, default: undefined },
open: { type: Boolean, default: undefined },
defaultOpen: { type: Boolean, default: undefined },
value: { type: [String, Number, Object, Array] as PropType<ValueType> },
defaultValue: { type: [String, Number, Object, Array] as PropType<ValueType> },
disabled: { type: Boolean, default: undefined },
placeholder: PropTypes.any,
/** @deprecated Use `searchValue` instead */
inputValue: String,
searchValue: String,
autoClearSearchValue: { type: Boolean, default: undefined },
maxTagPlaceholder: { type: Function as PropType<(omittedValues: LabelValueType[]) => any> },
fieldNames: { type: Object as PropType<FieldNames> },
loadData: { type: Function as PropType<(dataNode: LegacyDataNode) => Promise<unknown>> },
treeNodeFilterProp: String,
treeNodeLabelProp: String,
treeDataSimpleMode: {
type: [Boolean, Object] as PropType<boolean | SimpleModeConfig>,
default: undefined,
},
treeExpandedKeys: { type: [String, Number] as PropType<Key> },
treeDefaultExpandedKeys: { type: [String, Number] as PropType<Key> },
treeLoadedKeys: { type: [String, Number] as PropType<Key> },
treeCheckable: { type: Boolean, default: undefined },
treeCheckStrictly: { type: Boolean, default: undefined },
showCheckedStrategy: { type: String as PropType<CheckedStrategy> },
treeDefaultExpandAll: { type: Boolean, default: undefined },
treeData: { type: Array as PropType<DataNode[]> },
treeLine: { type: Boolean, default: undefined },
treeIcon: PropTypes.any,
showTreeIcon: { type: Boolean, default: undefined },
switcherIcon: PropTypes.any,
treeMotion: PropTypes.any,
children: PropTypes.any,
filterTreeNode: {
type: [Boolean, Function] as PropType<boolean | FilterFunc<LegacyDataNode>>,
default: undefined,
},
dropdownPopupAlign: PropTypes.any,
// Event
onSearch: { type: Function as PropType<(value: string) => void> },
onChange: {
type: Function as PropType<
(value: ValueType, labelList: any[], extra: ChangeEventExtra) => void
>,
},
onTreeExpand: { type: Function as PropType<(expandedKeys: Key[]) => void> },
onTreeLoad: { type: Function as PropType<(loadedKeys: Key[]) => void> },
onDropdownVisibleChange: { type: Function as PropType<(open: boolean) => void> },
// Legacy
/** `searchPlaceholder` has been removed since search box has been merged into input box */
searchPlaceholder: PropTypes.any,
/** @private This is not standard API since we only used in `rc-cascader`. Do not use in your production */
labelRender: { type: Function as PropType<(entity: FlattenDataNode) => any> },
};
}
class Helper<T> {
ReturnOptionListProps = optionListProps<T>();
ReturnTreeSelectProps = treeSelectProps<T>();
}
export type OptionListProps = Partial<ExtractPropTypes<Helper<DataNode>['ReturnOptionListProps']>>;
export type TreeSelectProps<T = DefaultValueType> = Partial<
ExtractPropTypes<Helper<T>['ReturnTreeSelectProps']>
>;

View File

@ -6,7 +6,6 @@
"ant-design-vue/es/*": ["components/*"]
},
"strictNullChecks": false,
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,