1
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import Affix from "./Affix";
|
||||
import SelectContent from "./Content";
|
||||
import SelectInputContext from "./context";
|
||||
import useBaseProps from "../hooks/useBaseProps";
|
||||
import { omit, useEvent } from '@rc-component/util';
|
||||
import KeyCode from "@rc-component/util/es/KeyCode";
|
||||
import { isValidateOpenKey } from "../utils/keyUtil";
|
||||
import { clsx } from 'clsx';
|
||||
import { getDOM } from "@rc-component/util/es/Dom/findDOMNode";
|
||||
import { composeRef } from "@rc-component/util/es/ref";
|
||||
import pickAttrs from "@rc-component/util/es/pickAttrs";
|
||||
const DEFAULT_OMIT_PROPS = ['value', 'onChange', 'removeIcon', 'placeholder', 'maxTagCount', 'maxTagTextLength', 'maxTagPlaceholder', 'choiceTransitionName', 'onInputKeyDown', 'onPopupScroll', 'tabIndex', 'activeValue', 'onSelectorRemove', 'focused'];
|
||||
export default /*#__PURE__*/React.forwardRef(function SelectInput(props, ref) {
|
||||
const {
|
||||
// Style
|
||||
prefixCls,
|
||||
className,
|
||||
style,
|
||||
// UI
|
||||
prefix,
|
||||
suffix,
|
||||
clearIcon,
|
||||
children,
|
||||
// Data
|
||||
multiple,
|
||||
displayValues,
|
||||
placeholder,
|
||||
mode,
|
||||
// Search
|
||||
searchValue,
|
||||
onSearch,
|
||||
onSearchSubmit,
|
||||
onInputBlur,
|
||||
// Input
|
||||
maxLength,
|
||||
autoFocus,
|
||||
// Events
|
||||
onMouseDown,
|
||||
onClearMouseDown,
|
||||
onInputKeyDown,
|
||||
onSelectorRemove,
|
||||
// Token handling
|
||||
tokenWithEnter,
|
||||
// Components
|
||||
components,
|
||||
...restProps
|
||||
} = props;
|
||||
const {
|
||||
triggerOpen,
|
||||
toggleOpen,
|
||||
showSearch,
|
||||
disabled,
|
||||
loading,
|
||||
classNames,
|
||||
styles
|
||||
} = useBaseProps();
|
||||
const rootRef = React.useRef(null);
|
||||
const inputRef = React.useRef(null);
|
||||
|
||||
// Handle keyboard events similar to original Selector
|
||||
const onInternalInputKeyDown = useEvent(event => {
|
||||
const {
|
||||
which
|
||||
} = event;
|
||||
|
||||
// Compatible with multiple lines in TextArea
|
||||
const isTextAreaElement = inputRef.current instanceof HTMLTextAreaElement;
|
||||
|
||||
// Prevent default behavior for up/down arrows when dropdown is open
|
||||
if (!isTextAreaElement && triggerOpen && (which === KeyCode.UP || which === KeyCode.DOWN)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Call the original onInputKeyDown callback
|
||||
if (onInputKeyDown) {
|
||||
onInputKeyDown(event);
|
||||
}
|
||||
|
||||
// Move within the text box for TextArea
|
||||
if (isTextAreaElement && !triggerOpen && ~[KeyCode.UP, KeyCode.DOWN, KeyCode.LEFT, KeyCode.RIGHT].indexOf(which)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open dropdown when a valid open key is pressed
|
||||
const isModifier = event.ctrlKey || event.altKey || event.metaKey;
|
||||
if (!isModifier && isValidateOpenKey(which)) {
|
||||
toggleOpen(true);
|
||||
}
|
||||
});
|
||||
|
||||
// ====================== Refs ======================
|
||||
React.useImperativeHandle(ref, () => {
|
||||
return {
|
||||
focus: options => {
|
||||
// Focus the inner input if available, otherwise fall back to root div.
|
||||
(inputRef.current || rootRef.current).focus?.(options);
|
||||
},
|
||||
blur: () => {
|
||||
(inputRef.current || rootRef.current).blur?.();
|
||||
},
|
||||
// Use getDOM to handle nested nativeElement structure (e.g., when RootComponent is antd Input)
|
||||
nativeElement: getDOM(rootRef.current)
|
||||
};
|
||||
});
|
||||
|
||||
// ====================== Open ======================
|
||||
const onInternalMouseDown = useEvent(event => {
|
||||
if (!disabled) {
|
||||
const inputDOM = getDOM(inputRef.current);
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/56002
|
||||
// Tell `useSelectTriggerControl` to ignore this event
|
||||
// When icon is dynamic render, the parentNode will miss
|
||||
// so we need to mark the event directly
|
||||
event.nativeEvent._ori_target = inputDOM;
|
||||
const isClickOnInput = inputDOM === event.target || inputDOM?.contains(event.target);
|
||||
if (inputDOM && !isClickOnInput) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Check if we should prevent closing when clicking on selector
|
||||
// Don't close if: open && not multiple && (combobox mode || showSearch)
|
||||
const shouldPreventCloseOnSingle = triggerOpen && !multiple && (mode === 'combobox' || showSearch);
|
||||
|
||||
// Don't close if: open && multiple && click on input
|
||||
const shouldPreventCloseOnMultipleInput = triggerOpen && multiple && isClickOnInput;
|
||||
const shouldPreventClose = shouldPreventCloseOnSingle || shouldPreventCloseOnMultipleInput;
|
||||
if (!event.nativeEvent._select_lazy) {
|
||||
inputRef.current?.focus();
|
||||
|
||||
// Only toggle open if we should not prevent close
|
||||
if (!shouldPreventClose) {
|
||||
toggleOpen();
|
||||
}
|
||||
} else if (triggerOpen) {
|
||||
// Lazy should also close when click clear icon
|
||||
toggleOpen(false);
|
||||
}
|
||||
}
|
||||
onMouseDown?.(event);
|
||||
});
|
||||
|
||||
// =================== Components ===================
|
||||
const {
|
||||
root: RootComponent
|
||||
} = components;
|
||||
|
||||
// ===================== Render =====================
|
||||
const domProps = omit(restProps, DEFAULT_OMIT_PROPS);
|
||||
const ariaProps = pickAttrs(domProps, {
|
||||
aria: true
|
||||
});
|
||||
const ariaKeys = Object.keys(ariaProps);
|
||||
|
||||
// Create context value with wrapped callbacks
|
||||
const contextValue = {
|
||||
...props,
|
||||
onInputKeyDown: onInternalInputKeyDown
|
||||
};
|
||||
if (RootComponent) {
|
||||
const originProps = RootComponent.props || {};
|
||||
const mergedProps = {
|
||||
...originProps,
|
||||
...domProps
|
||||
};
|
||||
Object.keys(originProps).forEach(key => {
|
||||
const originVal = originProps[key];
|
||||
const domVal = domProps[key];
|
||||
if (typeof originVal === 'function' && typeof domVal === 'function') {
|
||||
mergedProps[key] = (...args) => {
|
||||
domVal(...args);
|
||||
originVal(...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
if ( /*#__PURE__*/React.isValidElement(RootComponent)) {
|
||||
return /*#__PURE__*/React.cloneElement(RootComponent, {
|
||||
...mergedProps,
|
||||
ref: composeRef(RootComponent.ref, rootRef)
|
||||
});
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(RootComponent, _extends({}, mergedProps, {
|
||||
ref: rootRef
|
||||
}));
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(SelectInputContext.Provider, {
|
||||
value: contextValue
|
||||
}, /*#__PURE__*/React.createElement("div", _extends({}, omit(domProps, ariaKeys), {
|
||||
// Style
|
||||
ref: rootRef,
|
||||
className: className,
|
||||
style: style
|
||||
// Mouse Events
|
||||
,
|
||||
onMouseDown: onInternalMouseDown
|
||||
}), /*#__PURE__*/React.createElement(Affix, {
|
||||
className: clsx(`${prefixCls}-prefix`, classNames?.prefix),
|
||||
style: styles?.prefix
|
||||
}, prefix), /*#__PURE__*/React.createElement(SelectContent, {
|
||||
ref: inputRef
|
||||
}), /*#__PURE__*/React.createElement(Affix, {
|
||||
className: clsx(`${prefixCls}-suffix`, {
|
||||
[`${prefixCls}-suffix-loading`]: loading
|
||||
}, classNames?.suffix),
|
||||
style: styles?.suffix
|
||||
}, suffix), clearIcon && /*#__PURE__*/React.createElement(Affix, {
|
||||
className: clsx(`${prefixCls}-clear`, classNames?.clear),
|
||||
style: styles?.clear,
|
||||
onMouseDown: e => {
|
||||
// Mark to tell not trigger open or focus
|
||||
e.nativeEvent._select_lazy = true;
|
||||
onClearMouseDown?.(e);
|
||||
}
|
||||
}, clearIcon), children));
|
||||
});
|
||||
Reference in New Issue
Block a user