> {
- Group: typeof OptGroup;
- Option: typeof Options;
-}
-
-interface group {
- key?: string | React.Key;
- groupLabel?: string;
- groupValue?: string | number;
-}
-
-// ReactNode To Options
-function convertNodeToOption(node: React.ReactElement, group: group): OptionProps {
- const {
- props: { value, label, children, ...restProps },
- } = node as React.ReactElement;
- const { groupValue, groupLabel } = group;
- return { value, label: children !== undefined ? children : label, groupValue, groupLabel, ...restProps };
-}
-
-export function convertChildrenToData(nodes: React.ReactNode, group = {}): OptionProps[] {
- let nodeOptions: OptionProps[] = [];
- React.Children.forEach(nodes, (node: React.ReactElement) => {
- if (!React.isValidElement(node)) {
- return;
- }
- const {
- type: { isSelectOptGroup },
- props: { children, label, value },
- } = node as React.ReactElement & { type: { isSelectOptGroup?: boolean } }; // 联合类型
- if (!isSelectOptGroup) {
- // option
- nodeOptions.push(convertNodeToOption(node, group));
- } else {
- // Group
- nodeOptions = concat(nodeOptions, convertChildrenToData(children, { groupLabel: label, groupValue: value }));
- }
- });
- return nodeOptions;
-}
-
-export const CustomOption = (value: string | number, withGroup = false, id = customOptionKeyPrefix): OptionProps =>
- withGroup
+export const CustomOption = (value: string | number, withGroup = false, id = customOptionKeyPrefix): Option => {
+ return withGroup
? {
value,
label: value.toString(),
@@ -72,13 +40,13 @@ export const CustomOption = (value: string | number, withGroup = false, id = cus
value,
label: value.toString(),
};
-
+};
// provide search matching hightlight;
export const defaultLabelRenderer = (input: string, prefix: string) => (
- option: OptionProps,
+ option: Option,
isGroup: boolean
): React.ReactNode => {
- if (isGroup || typeof option.label !== 'string') return option.label;
+ if (isGroup || typeof option.label !== 'string') return option.title || option.label;
const index = option.label.indexOf(input);
return (
@@ -88,15 +56,13 @@ export const defaultLabelRenderer = (input: string, prefix: string) => (
);
};
-
-const defaultSearchPredicate = (input: string) => (o: OptionProps) => {
- return typeof o.label === 'string' ? o.label.includes(input) : true;
-};
-
-const defaultMatchPredicate = (input: string) => (o: OptionProps) => o.label === input;
-
+const defaultOptionLabelRenderer = (value: string | number, option?: Option) => option?.title || option?.label || value;
+const defaultSearchPredicate = (input: string) => (o: Option) =>
+ typeof o.label === 'string' ? o.label.includes(input) : true;
+const defaultMatchPredicate = (input: string) => (o: Option) => o.label === input;
const defaultNotFoundContent = (
-
);
-const defaultOptionLabelRenderer = (value: string | number, option?: OptionProps) => option?.label || value;
-
-const Select = React.forwardRef
((props: SelectProps, ref: React.MutableRefObject) => {
- const sizeContext = useContext(SizeContext);
- const {
- size = sizeContext || 'middle',
- options = [],
- multiple = false,
- allowClear = false,
- placeholder,
- searchable = false,
- disabled = false,
- allowCustomOption = false,
- notFoundContent = defaultNotFoundContent,
- customizePrefixCls,
- className,
- style,
- bordered = true,
- arrowComponent = defaultArrowComponent,
- autoWidth = true,
- listHeight,
- listRowHeight = defaultListRowHeight,
- labelRenderer = defaultLabelRenderer,
- searchPredicate = defaultSearchPredicate,
- matchPredicate = defaultMatchPredicate,
- optionLabelRenderer = defaultOptionLabelRenderer,
- defaultValue,
- value: controlledValue,
- onChange,
- onSearch,
- onSelect,
- onDeselect,
- getContainer,
- dropDownVisible,
- onDropDownVisibleChange,
- dropDownClassName,
- dropDownStyle,
- children,
- onClear,
- allowDeselect = false || multiple,
- } = props;
-
- const prefix = usePrefixCls('select', customizePrefixCls);
-
- const [unControlledValue, setUnControlledValue] = useState(defaultValue);
- const isControlled = !isNil(controlledValue);
- const value = isControlled ? controlledValue : unControlledValue;
- const [isFocused, setFocused] = useState(false);
- const [isHovered, setIsHovered] = useState(false);
- const [_visible, _setVisible] = useState(false);
- const visible = isNil(dropDownVisible) ? _visible : dropDownVisible;
- const setVisbile = isNil(onDropDownVisibleChange) ? _setVisible : onDropDownVisibleChange;
- const [input, setInput] = useState('');
- const inputRef = useRef(null);
- const [inputWidth, setInputWidth] = useState(2);
- const inputWidthRef = useRef(null);
- const selectRef = useRef(null)
- const selectorRef = ref || selectRef;
- const clearInput = () => {
- setInput('');
- };
-
- const onInputChange = (e: React.ChangeEvent) => {
- e.preventDefault();
- onSearch?.(e.target.value);
- setInput(e.target.value);
- };
- const onInputClick = (e: React.MouseEvent) => {
- if (input) e.stopPropagation();
- };
- const nodesToOptions = useMemo(() => convertChildrenToData(children), [children]);
- const [valueToOptionMap, hasGroup] = useMemo(() => {
- let group = false;
- const map = [...options, ...nodesToOptions].reduce((m, option) => {
- if (option.groupLabel) group = true;
- m.set(option.value, option);
- return m;
- }, new Map());
- return [map, group];
- }, [options, nodesToOptions]);
+interface CompoundedSelect extends React.ForwardRefExoticComponent> {
+ Group: typeof OptGroup;
+ Option: typeof Options;
+}
- useEffect(() => {
- if (!disabled) {
- setFocused(visible);
- if (visible) {
- if (inputRef.current) {
- inputRef.current.focus();
+const Select = React.forwardRef(
+ (props: SelectProps, ref: React.MutableRefObject) => {
+ const sizeContext = useContext(SizeContext);
+ const {
+ size = sizeContext || 'middle',
+ options = [],
+ defaultValue = null,
+ value: controlledValue,
+ multiple = false,
+ allowClear = false,
+ placeholder,
+ searchable = false,
+ disabled = false,
+ bordered = true,
+ allowCustomOption = false,
+ autoWidth = true,
+ allowDeselect = false || multiple,
+ notFoundContent = defaultNotFoundContent,
+ customizePrefixCls,
+ className,
+ style,
+ optionStyle,
+ groupStyle,
+ arrowComponent,
+ closeComponent,
+ listHeight = defaultListHeight,
+ listRowHeight = defaultListRowHeight,
+ labelRenderer = defaultLabelRenderer,
+ searchPredicate = defaultSearchPredicate,
+ matchPredicate = defaultMatchPredicate,
+ optionLabelRenderer = defaultOptionLabelRenderer,
+ onChange,
+ onSearch,
+ onSelect,
+ onDeselect,
+ onClear,
+ getContainer,
+ dropDownVisible,
+ onDropDownVisibleChange,
+ dropDownClassName,
+ dropDownStyle,
+ children,
+ } = props;
+
+ const prefix = usePrefixCls('select', customizePrefixCls);
+ const [unControlledValue, setUnControlledValue] = useState(defaultValue);
+ const isControlled = !isNil(controlledValue);
+ const value = isControlled ? controlledValue : unControlledValue;
+ const [isFocused, setFocused] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+ const [_visible, _setVisible] = useState(false);
+ const visible = isNil(dropDownVisible) ? _visible : dropDownVisible;
+ const setVisbile = isNil(onDropDownVisibleChange) ? _setVisible : onDropDownVisibleChange;
+ const [input, setInput] = useState('');
+ const inputRef = useRef(null);
+ const [inputWidth, setInputWidth] = useState(2);
+ const inputWidthRef = useRef(null);
+ const selectRef = useRef(null);
+ const selectorRef = ref || selectRef;
+
+ const clearInput = () => {
+ setInput('');
+ };
+ /** merge Option & nodeOption & mergedOptionsMap */
+ const nodesToOptions = useMemo(() => convertChildrenToData(children), [children]);
+
+ const [flattenOptionsMap, mergedFlattenOPtions, hasGroup] = useMemo(() => {
+ const group = !![...options, ...nodesToOptions].find((v) =>
+ Object.prototype.hasOwnProperty.call(v, 'groupValue')
+ );
+ const flattenOptions: Option[] = [];
+ const optionsMap = [...options, ...nodesToOptions].reduce((m, option: Option) => {
+ const { groupLabel: optionGroupLabel, groupValue: optionGroupValue, value: optionValue } = option;
+ if (group && !optionGroupValue && !optionGroupLabel) {
+ const ungroupedOption = {
+ groupLabel: ungroupedOptionLabel,
+ groupValue: ungroupedOptionKey,
+ ...option,
+ };
+ m.set(optionValue, ungroupedOption);
+ flattenOptions.push(ungroupedOption);
+ } else {
+ m.set(optionValue, option);
+ flattenOptions.push(option);
}
+ return m;
+ }, new Map());
+ return [optionsMap, flattenOptions, group];
+ }, [options, nodesToOptions]);
+
+ // input
+ const onInputChange = (e: React.ChangeEvent) => {
+ e.preventDefault();
+ onSearch?.(e.target.value);
+ setInput(e.target.value);
+ };
+ const onInputClick = (e: React.MouseEvent) => {
+ if (input) e.stopPropagation();
+ };
+
+ useEffect(() => {
+ if (inputWidthRef.current) {
+ setInputWidth(inputWidthRef.current?.getBoundingClientRect().width + 4);
}
- }
- }, [visible, disabled]);
-
- const onVisibleChange = (optVisible: boolean) => {
- if (!disabled) {
- setVisbile(optVisible);
- if (!optVisible) {
- setTimeout(clearInput, 0);
- inputRef.current?.blur();
+ }, [input]);
+
+ useEffect(() => {
+ if (!disabled) {
+ setFocused(visible);
+ if (visible) {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }
}
- }
- };
-
- const getOptionByValue = useCallback(
- (optValue: string | number): OptionProps | undefined => {
- return valueToOptionMap.get(optValue);
- },
- [valueToOptionMap]
- );
-
- const getOptionsByValue = (optValue: MaybeArray): MaybeArray | undefined => {
- return Array.isArray(optValue)
- ? optValue.reduce((prev: OptionProps[], v) => {
+ }, [visible, disabled]);
+
+ const onVisibleChange = (optVisible: boolean) => {
+ if (!disabled) {
+ setVisbile(optVisible);
+ if (!optVisible) {
+ setTimeout(clearInput, 0);
+ inputRef.current?.blur();
+ }
+ }
+ };
+ // value to option || options
+ const getOptionByValue = useCallback(
+ (optValue: string | number): Option => {
+ return flattenOptionsMap.get(optValue);
+ },
+ [flattenOptionsMap]
+ );
+
+ const getOptionsByValue = (optValue: MaybeArray): MaybeArray
renders components 2`] = `