217 lines
7.2 KiB
JavaScript
217 lines
7.2 KiB
JavaScript
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));
|
|
}); |