ant-design/components/auto-complete/AutoComplete.tsx
Jony J b3af097e8c
refactor: semantic dom structure (#53695)
* refactor: semantic dom structure

* fix: ci
2025-04-29 11:46:11 +08:00

216 lines
6.5 KiB
TypeScript

import * as React from 'react';
import cls from 'classnames';
import type { BaseSelectRef } from 'rc-select';
import toArray from 'rc-util/lib/Children/toArray';
import omit from 'rc-util/lib/omit';
import { useZIndex } from '../_util/hooks/useZIndex';
import type { InputStatus } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import type {
BaseOptionType,
DefaultOptionType,
InternalSelectProps,
RefSelectProps,
SelectProps,
} from '../select';
import Select from '../select';
const { Option } = Select;
export interface DataSourceItemObject {
value: string;
text: string;
}
export type DataSourceItemType = DataSourceItemObject | React.ReactNode;
export interface AutoCompleteProps<
ValueType = any,
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
> extends Omit<
InternalSelectProps<ValueType, OptionType>,
'loading' | 'mode' | 'optionLabelProp' | 'labelInValue'
> {
/** @deprecated Please use `options` instead */
dataSource?: DataSourceItemType[];
status?: InputStatus;
/** @deprecated Please use `classNames.popup.root` instead */
popupClassName?: string;
/** @deprecated Please use `classNames.popup.root` instead */
dropdownClassName?: string;
/** @deprecated Please use `popupMatchSelectWidth` instead */
dropdownMatchSelectWidth?: boolean | number;
popupMatchSelectWidth?: boolean | number;
/** @deprecated Please use `popupRender` instead */
dropdownRender?: (menu: React.ReactElement) => React.ReactElement;
popupRender?: (menu: React.ReactElement) => React.ReactElement;
/** @deprecated Please use `styles.popup.root` instead */
dropdownStyle?: React.CSSProperties;
/** @deprecated Please use `onOpenChange` instead */
onDropdownVisibleChange?: (visible: boolean) => void;
onOpenChange?: (visible: boolean) => void;
}
function isSelectOptionOrSelectOptGroup(child: any): boolean {
return child?.type && (child.type.isSelectOption || child.type.isSelectOptGroup);
}
const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteProps> = (
props,
ref,
) => {
const {
prefixCls: customizePrefixCls,
className,
popupClassName,
dropdownClassName,
children,
dataSource,
dropdownStyle,
dropdownRender,
popupRender,
onDropdownVisibleChange,
onOpenChange,
styles,
classNames,
} = props;
const childNodes: React.ReactElement[] = toArray(children);
const mergedPopupStyle = styles?.popup?.root || dropdownStyle;
const mergedPopupClassName = classNames?.popup?.root || popupClassName || dropdownClassName;
const mergedPopupRender = popupRender || dropdownRender;
const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange;
// ============================= Input =============================
let customizeInput: React.ReactElement | undefined;
if (
childNodes.length === 1 &&
React.isValidElement(childNodes[0]) &&
!isSelectOptionOrSelectOptGroup(childNodes[0])
) {
[customizeInput] = childNodes;
}
const getInputElement = customizeInput ? (): React.ReactElement => customizeInput! : undefined;
// ============================ Options ============================
let optionChildren: React.ReactNode;
// [Legacy] convert `children` or `dataSource` into option children
if (childNodes.length && isSelectOptionOrSelectOptGroup(childNodes[0])) {
optionChildren = children;
} else {
optionChildren = dataSource
? dataSource.map((item) => {
if (React.isValidElement(item)) {
return item;
}
switch (typeof item) {
case 'string':
return (
<Option key={item} value={item}>
{item}
</Option>
);
case 'object': {
const { value: optionValue } = item as DataSourceItemObject;
return (
<Option key={optionValue} value={optionValue}>
{(item as DataSourceItemObject).text}
</Option>
);
}
default:
return undefined;
}
})
: [];
}
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('AutoComplete');
warning(
!customizeInput || !('size' in props),
'usage',
'You need to control style self instead of setting `size` when using customize input.',
);
const deprecatedProps = {
dropdownMatchSelectWidth: 'popupMatchSelectWidth',
dropdownStyle: 'styles.popup.root',
dropdownClassName: 'classNames.popup.root',
popupClassName: 'classNames.popup.root',
dropdownRender: 'popupRender',
onDropdownVisibleChange: 'onOpenChange',
dataSource: 'options',
};
Object.entries(deprecatedProps).forEach(([oldProp, newProp]) => {
warning.deprecated(!(oldProp in props), oldProp, newProp);
});
}
const { getPrefixCls } = React.useContext<ConfigConsumerProps>(ConfigContext);
const prefixCls = getPrefixCls('select', customizePrefixCls);
// ============================ zIndex ============================
const [zIndex] = useZIndex('SelectLike', mergedPopupStyle?.zIndex as number);
return (
<Select
ref={ref}
suffixIcon={null}
{...omit(props, ['dataSource', 'dropdownClassName', 'popupClassName'])}
prefixCls={prefixCls}
classNames={{
popup: {
root: mergedPopupClassName,
},
root: classNames?.root,
}}
styles={{
popup: {
root: {
...mergedPopupStyle,
zIndex,
},
},
root: styles?.root,
}}
className={cls(`${prefixCls}-auto-complete`, className)}
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE as SelectProps['mode']}
popupRender={mergedPopupRender}
onOpenChange={mergedOnOpenChange}
{...{
// Internal api
getInputElement,
}}
>
{optionChildren}
</Select>
);
};
const RefAutoComplete = React.forwardRef<RefSelectProps, AutoCompleteProps>(
AutoComplete,
) as unknown as (<
ValueType = any,
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
>(
props: React.PropsWithChildren<AutoCompleteProps<ValueType, OptionType>> &
React.RefAttributes<BaseSelectRef>,
) => React.ReactElement) & {
displayName?: string;
};
if (process.env.NODE_ENV !== 'production') {
RefAutoComplete.displayName = 'AutoComplete';
}
export default RefAutoComplete;