1
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
export interface AffixProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export default function Affix(props: AffixProps): React.JSX.Element;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
// Affix is a simple wrapper which should not read context or logical props
|
||||
export default function Affix(props) {
|
||||
const {
|
||||
children,
|
||||
...restProps
|
||||
} = props;
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement("div", restProps, children);
|
||||
}
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import type { SharedContentProps } from '.';
|
||||
declare const _default: React.ForwardRefExoticComponent<SharedContentProps & React.RefAttributes<HTMLInputElement>>;
|
||||
export default _default;
|
||||
Generated
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
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 { clsx } from 'clsx';
|
||||
import Overflow from '@rc-component/overflow';
|
||||
import Input from "../Input";
|
||||
import { useSelectInputContext } from "../context";
|
||||
import TransBtn from "../../TransBtn";
|
||||
import { getTitle } from "../../utils/commonUtil";
|
||||
import useBaseProps from "../../hooks/useBaseProps";
|
||||
import Placeholder from "./Placeholder";
|
||||
function itemKey(value) {
|
||||
return value.key ?? value.value;
|
||||
}
|
||||
const onPreventMouseDown = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
export default /*#__PURE__*/React.forwardRef(function MultipleContent({
|
||||
inputProps
|
||||
}, ref) {
|
||||
const {
|
||||
prefixCls,
|
||||
displayValues,
|
||||
searchValue,
|
||||
mode,
|
||||
onSelectorRemove,
|
||||
removeIcon: removeIconFromContext
|
||||
} = useSelectInputContext();
|
||||
const {
|
||||
disabled,
|
||||
showSearch,
|
||||
triggerOpen,
|
||||
rawOpen,
|
||||
toggleOpen,
|
||||
autoClearSearchValue,
|
||||
tagRender: tagRenderFromContext,
|
||||
maxTagPlaceholder: maxTagPlaceholderFromContext,
|
||||
maxTagTextLength,
|
||||
maxTagCount,
|
||||
classNames,
|
||||
styles
|
||||
} = useBaseProps();
|
||||
const selectionItemPrefixCls = `${prefixCls}-selection-item`;
|
||||
|
||||
// ===================== Search ======================
|
||||
// Apply autoClearSearchValue logic: when dropdown is closed and autoClearSearchValue is not false (default true), clear search value
|
||||
// Use rawOpen to avoid clearing search when emptyListContent blocks open
|
||||
let computedSearchValue = searchValue;
|
||||
if (!rawOpen && mode === 'multiple' && autoClearSearchValue !== false) {
|
||||
computedSearchValue = '';
|
||||
}
|
||||
const inputValue = showSearch ? computedSearchValue || '' : '';
|
||||
const inputEditable = showSearch && !disabled;
|
||||
|
||||
// Props from context with safe defaults
|
||||
const removeIcon = removeIconFromContext ?? '×';
|
||||
const maxTagPlaceholder = maxTagPlaceholderFromContext ?? (omittedValues => `+ ${omittedValues.length} ...`);
|
||||
const tagRender = tagRenderFromContext;
|
||||
const onToggleOpen = newOpen => {
|
||||
toggleOpen(newOpen);
|
||||
};
|
||||
const onRemove = value => {
|
||||
onSelectorRemove?.(value);
|
||||
};
|
||||
|
||||
// ======================== Item ========================
|
||||
// >>> Render Selector Node. Includes Item & Rest
|
||||
const defaultRenderSelector = (item, content, itemDisabled, closable, onClose) => /*#__PURE__*/React.createElement("span", {
|
||||
title: getTitle(item),
|
||||
className: clsx(selectionItemPrefixCls, {
|
||||
[`${selectionItemPrefixCls}-disabled`]: itemDisabled
|
||||
}, classNames?.item),
|
||||
style: styles?.item
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: clsx(`${selectionItemPrefixCls}-content`, classNames?.itemContent),
|
||||
style: styles?.itemContent
|
||||
}, content), closable && /*#__PURE__*/React.createElement(TransBtn, {
|
||||
className: clsx(`${selectionItemPrefixCls}-remove`, classNames?.itemRemove),
|
||||
style: styles?.itemRemove,
|
||||
onMouseDown: onPreventMouseDown,
|
||||
onClick: onClose,
|
||||
customizeIcon: removeIcon
|
||||
}, "\xD7"));
|
||||
const customizeRenderSelector = (value, content, itemDisabled, closable, onClose, isMaxTag, info) => {
|
||||
const onMouseDown = e => {
|
||||
onPreventMouseDown(e);
|
||||
onToggleOpen(!triggerOpen);
|
||||
};
|
||||
return /*#__PURE__*/React.createElement("span", {
|
||||
onMouseDown: onMouseDown
|
||||
}, tagRender({
|
||||
label: content,
|
||||
value,
|
||||
index: info?.index,
|
||||
disabled: itemDisabled,
|
||||
closable,
|
||||
onClose,
|
||||
isMaxTag: !!isMaxTag
|
||||
}));
|
||||
};
|
||||
|
||||
// ====================== Overflow ======================
|
||||
const renderItem = (valueItem, info) => {
|
||||
const {
|
||||
disabled: itemDisabled,
|
||||
label,
|
||||
value
|
||||
} = valueItem;
|
||||
const closable = !disabled && !itemDisabled;
|
||||
let displayLabel = label;
|
||||
if (typeof maxTagTextLength === 'number') {
|
||||
if (typeof label === 'string' || typeof label === 'number') {
|
||||
const strLabel = String(displayLabel);
|
||||
if (strLabel.length > maxTagTextLength) {
|
||||
displayLabel = `${strLabel.slice(0, maxTagTextLength)}...`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const onClose = event => {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
onRemove(valueItem);
|
||||
};
|
||||
return typeof tagRender === 'function' ? customizeRenderSelector(value, displayLabel, itemDisabled, closable, onClose, undefined, info) : defaultRenderSelector(valueItem, displayLabel, itemDisabled, closable, onClose);
|
||||
};
|
||||
const renderRest = omittedValues => {
|
||||
// https://github.com/ant-design/ant-design/issues/48930
|
||||
if (!displayValues.length) {
|
||||
return null;
|
||||
}
|
||||
const content = typeof maxTagPlaceholder === 'function' ? maxTagPlaceholder(omittedValues) : maxTagPlaceholder;
|
||||
return typeof tagRender === 'function' ? customizeRenderSelector(undefined, content, false, false, undefined, true) : defaultRenderSelector({
|
||||
title: content
|
||||
}, content, false);
|
||||
};
|
||||
|
||||
// ======================= Render =======================
|
||||
return /*#__PURE__*/React.createElement(Overflow, {
|
||||
prefixCls: `${prefixCls}-content`,
|
||||
className: classNames?.content,
|
||||
style: styles?.content,
|
||||
prefix: !displayValues.length && !inputValue && /*#__PURE__*/React.createElement(Placeholder, null),
|
||||
data: displayValues,
|
||||
renderItem: renderItem,
|
||||
renderRest: renderRest,
|
||||
suffix: /*#__PURE__*/React.createElement(Input, _extends({
|
||||
ref: ref,
|
||||
disabled: disabled,
|
||||
readOnly: !inputEditable
|
||||
}, inputProps, {
|
||||
value: inputValue || '',
|
||||
syncWidth: true
|
||||
})),
|
||||
itemKey: itemKey,
|
||||
maxCount: maxTagCount
|
||||
});
|
||||
});
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
export interface PlaceholderProps {
|
||||
show?: boolean;
|
||||
}
|
||||
export default function Placeholder(props: PlaceholderProps): React.JSX.Element;
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useSelectInputContext } from "../context";
|
||||
import useBaseProps from "../../hooks/useBaseProps";
|
||||
export default function Placeholder(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
placeholder,
|
||||
displayValues
|
||||
} = useSelectInputContext();
|
||||
const {
|
||||
classNames,
|
||||
styles
|
||||
} = useBaseProps();
|
||||
const {
|
||||
show = true
|
||||
} = props;
|
||||
if (displayValues.length) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-placeholder`, classNames?.placeholder),
|
||||
style: {
|
||||
visibility: show ? 'visible' : 'hidden',
|
||||
...styles?.placeholder
|
||||
}
|
||||
}, placeholder);
|
||||
}
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import type { SharedContentProps } from '.';
|
||||
declare const SingleContent: React.ForwardRefExoticComponent<SharedContentProps & React.RefAttributes<HTMLInputElement>>;
|
||||
export default SingleContent;
|
||||
Generated
Vendored
+102
@@ -0,0 +1,102 @@
|
||||
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 { clsx } from 'clsx';
|
||||
import Input from "../Input";
|
||||
import { useSelectInputContext } from "../context";
|
||||
import useBaseProps from "../../hooks/useBaseProps";
|
||||
import Placeholder from "./Placeholder";
|
||||
import SelectContext from "../../SelectContext";
|
||||
import { getTitle } from "../../utils/commonUtil";
|
||||
const SingleContent = /*#__PURE__*/React.forwardRef(({
|
||||
inputProps
|
||||
}, ref) => {
|
||||
const {
|
||||
prefixCls,
|
||||
searchValue,
|
||||
activeValue,
|
||||
displayValues,
|
||||
maxLength,
|
||||
mode,
|
||||
components
|
||||
} = useSelectInputContext();
|
||||
const {
|
||||
triggerOpen,
|
||||
title: rootTitle,
|
||||
showSearch,
|
||||
classNames,
|
||||
styles
|
||||
} = useBaseProps();
|
||||
const selectContext = React.useContext(SelectContext);
|
||||
const [inputChanged, setInputChanged] = React.useState(false);
|
||||
const combobox = mode === 'combobox';
|
||||
const displayValue = displayValues[0];
|
||||
|
||||
// Implement the same logic as the old SingleSelector
|
||||
const mergedSearchValue = React.useMemo(() => {
|
||||
if (combobox && activeValue && !inputChanged && triggerOpen) {
|
||||
return activeValue;
|
||||
}
|
||||
return showSearch ? searchValue : '';
|
||||
}, [combobox, activeValue, inputChanged, triggerOpen, searchValue, showSearch]);
|
||||
const [optionClassName, optionStyle, optionTitle, hasOptionStyle] = React.useMemo(() => {
|
||||
let className;
|
||||
let style;
|
||||
let titleValue;
|
||||
if (displayValue && selectContext?.flattenOptions) {
|
||||
const option = selectContext.flattenOptions.find(opt => opt.value === displayValue.value);
|
||||
if (option?.data) {
|
||||
className = option.data.className;
|
||||
style = option.data.style;
|
||||
titleValue = getTitle(option.data);
|
||||
}
|
||||
}
|
||||
if (displayValue && !titleValue) {
|
||||
titleValue = getTitle(displayValue);
|
||||
}
|
||||
if (rootTitle !== undefined) {
|
||||
titleValue = rootTitle;
|
||||
}
|
||||
const nextHasStyle = !!className || !!style;
|
||||
return [className, style, titleValue, nextHasStyle];
|
||||
}, [displayValue, selectContext?.flattenOptions, rootTitle]);
|
||||
React.useEffect(() => {
|
||||
if (combobox) {
|
||||
setInputChanged(false);
|
||||
}
|
||||
}, [combobox, activeValue]);
|
||||
|
||||
// ========================== Render ==========================
|
||||
const showHasValueCls = displayValue && displayValue.label !== null && displayValue.label !== undefined && String(displayValue.label).trim() !== '';
|
||||
|
||||
// Render value
|
||||
// Only render value when not using custom input in combobox mode
|
||||
const shouldRenderValue = !(combobox && components?.input);
|
||||
const renderValue = shouldRenderValue ? displayValue ? hasOptionStyle ? /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-content-value`, optionClassName),
|
||||
style: {
|
||||
...(mergedSearchValue ? {
|
||||
visibility: 'hidden'
|
||||
} : {}),
|
||||
...optionStyle
|
||||
},
|
||||
title: optionTitle
|
||||
}, displayValue.label) : displayValue.label : /*#__PURE__*/React.createElement(Placeholder, {
|
||||
show: !mergedSearchValue
|
||||
}) : null;
|
||||
// Render
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-content`, showHasValueCls && `${prefixCls}-content-has-value`, mergedSearchValue && `${prefixCls}-content-has-search-value`, hasOptionStyle && `${prefixCls}-content-has-option-style`, classNames?.content),
|
||||
style: styles?.content,
|
||||
title: hasOptionStyle ? undefined : optionTitle
|
||||
}, renderValue, /*#__PURE__*/React.createElement(Input, _extends({
|
||||
ref: ref
|
||||
}, inputProps, {
|
||||
value: mergedSearchValue,
|
||||
maxLength: mode === 'combobox' ? maxLength : undefined,
|
||||
onChange: e => {
|
||||
setInputChanged(true);
|
||||
inputProps.onChange?.(e);
|
||||
}
|
||||
})));
|
||||
});
|
||||
export default SingleContent;
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
export interface SharedContentProps {
|
||||
inputProps: React.InputHTMLAttributes<HTMLInputElement>;
|
||||
}
|
||||
declare const SelectContent: React.ForwardRefExoticComponent<React.RefAttributes<HTMLInputElement>>;
|
||||
export default SelectContent;
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import pickAttrs from "@rc-component/util/es/pickAttrs";
|
||||
import SingleContent from "./SingleContent";
|
||||
import MultipleContent from "./MultipleContent";
|
||||
import { useSelectInputContext } from "../context";
|
||||
import useBaseProps from "../../hooks/useBaseProps";
|
||||
const SelectContent = /*#__PURE__*/React.forwardRef(function SelectContent(_, ref) {
|
||||
const {
|
||||
multiple,
|
||||
onInputKeyDown,
|
||||
tabIndex
|
||||
} = useSelectInputContext();
|
||||
const baseProps = useBaseProps();
|
||||
const {
|
||||
showSearch
|
||||
} = baseProps;
|
||||
const ariaProps = pickAttrs(baseProps, {
|
||||
aria: true
|
||||
});
|
||||
const sharedInputProps = {
|
||||
...ariaProps,
|
||||
onKeyDown: onInputKeyDown,
|
||||
readOnly: !showSearch,
|
||||
tabIndex
|
||||
};
|
||||
if (multiple) {
|
||||
return /*#__PURE__*/React.createElement(MultipleContent, {
|
||||
ref: ref,
|
||||
inputProps: sharedInputProps
|
||||
});
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(SingleContent, {
|
||||
ref: ref,
|
||||
inputProps: sharedInputProps
|
||||
});
|
||||
});
|
||||
export default SelectContent;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
export interface InputProps {
|
||||
id?: string;
|
||||
readOnly?: boolean;
|
||||
value?: string;
|
||||
onChange?: React.ChangeEventHandler<HTMLInputElement>;
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
|
||||
onFocus?: React.FocusEventHandler<HTMLInputElement>;
|
||||
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
maxLength?: number;
|
||||
/** width always match content width */
|
||||
syncWidth?: boolean;
|
||||
/** autoComplete for input */
|
||||
autoComplete?: string;
|
||||
}
|
||||
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
||||
export default Input;
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useSelectInputContext } from "./context";
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import useBaseProps from "../hooks/useBaseProps";
|
||||
import { composeRef } from "@rc-component/util/es/ref";
|
||||
const Input = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
style,
|
||||
syncWidth,
|
||||
value,
|
||||
className,
|
||||
autoComplete,
|
||||
...restProps
|
||||
} = props;
|
||||
const {
|
||||
prefixCls,
|
||||
mode,
|
||||
onSearch,
|
||||
onSearchSubmit,
|
||||
onInputBlur,
|
||||
autoFocus,
|
||||
tokenWithEnter,
|
||||
placeholder,
|
||||
components: {
|
||||
input: InputComponent = 'input'
|
||||
}
|
||||
} = useSelectInputContext();
|
||||
const {
|
||||
id,
|
||||
classNames,
|
||||
styles,
|
||||
open,
|
||||
activeDescendantId,
|
||||
role,
|
||||
disabled
|
||||
} = useBaseProps() || {};
|
||||
const inputCls = clsx(`${prefixCls}-input`, classNames?.input, className);
|
||||
|
||||
// Used to handle input method composition status
|
||||
const compositionStatusRef = React.useRef(false);
|
||||
|
||||
// Used to handle paste content, similar to original Selector implementation
|
||||
const pastedTextRef = React.useRef(null);
|
||||
|
||||
// ============================== Refs ==============================
|
||||
const inputRef = React.useRef(null);
|
||||
React.useImperativeHandle(ref, () => inputRef.current);
|
||||
|
||||
// ============================== Data ==============================
|
||||
// Handle input changes
|
||||
const handleChange = event => {
|
||||
let {
|
||||
value: nextVal
|
||||
} = event.target;
|
||||
|
||||
// Handle pasted text with tokenWithEnter, similar to original Selector implementation
|
||||
if (tokenWithEnter && pastedTextRef.current && /[\r\n]/.test(pastedTextRef.current)) {
|
||||
// CRLF will be treated as a single space for input element
|
||||
const replacedText = pastedTextRef.current.replace(/[\r\n]+$/, '').replace(/\r\n/g, ' ').replace(/[\r\n]/g, ' ');
|
||||
nextVal = nextVal.replace(replacedText, pastedTextRef.current);
|
||||
}
|
||||
|
||||
// Reset pasted text reference
|
||||
pastedTextRef.current = null;
|
||||
|
||||
// Call onSearch callback
|
||||
if (onSearch) {
|
||||
onSearch(nextVal, true, compositionStatusRef.current);
|
||||
}
|
||||
|
||||
// Call original onChange callback
|
||||
onChange?.(event);
|
||||
};
|
||||
|
||||
// ============================ Keyboard ============================
|
||||
// Handle keyboard events
|
||||
const handleKeyDown = event => {
|
||||
const {
|
||||
key
|
||||
} = event;
|
||||
const {
|
||||
value: nextVal
|
||||
} = event.currentTarget;
|
||||
|
||||
// Handle Enter key submission - referencing Selector implementation
|
||||
if (key === 'Enter' && mode === 'tags' && !open && !compositionStatusRef.current && onSearchSubmit) {
|
||||
onSearchSubmit(nextVal);
|
||||
}
|
||||
|
||||
// Call original onKeyDown callback
|
||||
onKeyDown?.(event);
|
||||
};
|
||||
|
||||
// Handle blur events
|
||||
const handleBlur = event => {
|
||||
// Call onInputBlur callback
|
||||
onInputBlur?.();
|
||||
|
||||
// Call original onBlur callback
|
||||
onBlur?.(event);
|
||||
};
|
||||
|
||||
// Handle input method composition start
|
||||
const handleCompositionStart = () => {
|
||||
compositionStatusRef.current = true;
|
||||
};
|
||||
|
||||
// Handle input method composition end
|
||||
const handleCompositionEnd = event => {
|
||||
compositionStatusRef.current = false;
|
||||
|
||||
// Trigger search when input method composition ends, similar to original Selector
|
||||
if (mode !== 'combobox') {
|
||||
const {
|
||||
value: nextVal
|
||||
} = event.currentTarget;
|
||||
onSearch?.(nextVal, true, false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle paste events to track pasted content
|
||||
const handlePaste = event => {
|
||||
const {
|
||||
clipboardData
|
||||
} = event;
|
||||
const pastedValue = clipboardData?.getData('text');
|
||||
pastedTextRef.current = pastedValue || '';
|
||||
};
|
||||
|
||||
// ============================= Width ==============================
|
||||
const [widthCssVar, setWidthCssVar] = React.useState(undefined);
|
||||
|
||||
// When syncWidth is enabled, adjust input width based on content
|
||||
useLayoutEffect(() => {
|
||||
const input = inputRef.current;
|
||||
if (syncWidth && input) {
|
||||
input.style.width = '0px';
|
||||
const scrollWidth = input.scrollWidth;
|
||||
setWidthCssVar(scrollWidth);
|
||||
|
||||
// Reset input style
|
||||
input.style.width = '';
|
||||
}
|
||||
}, [syncWidth, value]);
|
||||
|
||||
// ============================= Render =============================
|
||||
// Extract shared input props
|
||||
const sharedInputProps = {
|
||||
id,
|
||||
type: mode === 'combobox' ? 'text' : 'search',
|
||||
...restProps,
|
||||
ref: inputRef,
|
||||
style: {
|
||||
...styles?.input,
|
||||
...style,
|
||||
'--select-input-width': widthCssVar
|
||||
},
|
||||
autoFocus,
|
||||
autoComplete: autoComplete || 'off',
|
||||
className: inputCls,
|
||||
disabled,
|
||||
value: value || '',
|
||||
onChange: handleChange,
|
||||
onKeyDown: handleKeyDown,
|
||||
onBlur: handleBlur,
|
||||
onPaste: handlePaste,
|
||||
onCompositionStart: handleCompositionStart,
|
||||
onCompositionEnd: handleCompositionEnd,
|
||||
// Accessibility attributes
|
||||
role: role || 'combobox',
|
||||
'aria-expanded': open || false,
|
||||
'aria-haspopup': 'listbox',
|
||||
'aria-owns': open ? `${id}_list` : undefined,
|
||||
'aria-autocomplete': 'list',
|
||||
'aria-controls': open ? `${id}_list` : undefined,
|
||||
'aria-activedescendant': open ? activeDescendantId : undefined
|
||||
};
|
||||
|
||||
// Handle different InputComponent types
|
||||
if ( /*#__PURE__*/React.isValidElement(InputComponent)) {
|
||||
// If InputComponent is a ReactElement, use cloneElement with merged props
|
||||
const existingProps = InputComponent.props || {};
|
||||
|
||||
// Start with shared props as base
|
||||
const mergedProps = {
|
||||
placeholder: props.placeholder || placeholder,
|
||||
...sharedInputProps,
|
||||
...existingProps
|
||||
};
|
||||
|
||||
// Batch update function calls
|
||||
Object.keys(existingProps).forEach(key => {
|
||||
const existingValue = existingProps[key];
|
||||
if (typeof existingValue === 'function') {
|
||||
// Merge event handlers
|
||||
mergedProps[key] = (...args) => {
|
||||
existingValue(...args);
|
||||
sharedInputProps[key]?.(...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Update ref
|
||||
mergedProps.ref = composeRef(InputComponent.ref, sharedInputProps.ref);
|
||||
return /*#__PURE__*/React.cloneElement(InputComponent, mergedProps);
|
||||
}
|
||||
|
||||
// If InputComponent is a component type, render normally
|
||||
const Component = InputComponent;
|
||||
return /*#__PURE__*/React.createElement(Component, sharedInputProps);
|
||||
});
|
||||
export default Input;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type { SelectInputProps } from '.';
|
||||
export type ContentContextProps = SelectInputProps;
|
||||
declare const SelectInputContext: React.Context<SelectInputProps>;
|
||||
export declare function useSelectInputContext(): SelectInputProps;
|
||||
export default SelectInputContext;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
const SelectInputContext = /*#__PURE__*/React.createContext(null);
|
||||
export function useSelectInputContext() {
|
||||
return React.useContext(SelectInputContext);
|
||||
}
|
||||
export default SelectInputContext;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import * as React from 'react';
|
||||
import type { DisplayValueType, Mode, RenderNode } from '../interface';
|
||||
import type { ComponentsConfig } from '../hooks/useComponents';
|
||||
export interface SelectInputRef {
|
||||
focus: (options?: FocusOptions) => void;
|
||||
blur: () => void;
|
||||
nativeElement: HTMLDivElement;
|
||||
}
|
||||
export interface SelectInputProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'prefix'> {
|
||||
prefixCls: string;
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
clearIcon?: React.ReactNode;
|
||||
removeIcon?: RenderNode;
|
||||
multiple?: boolean;
|
||||
displayValues: DisplayValueType[];
|
||||
placeholder?: React.ReactNode;
|
||||
searchValue?: string;
|
||||
activeValue?: string;
|
||||
mode?: Mode;
|
||||
autoClearSearchValue?: boolean;
|
||||
onSearch?: (searchText: string, fromTyping: boolean, isCompositing: boolean) => void;
|
||||
onSearchSubmit?: (searchText: string) => void;
|
||||
onInputBlur?: () => void;
|
||||
onClearMouseDown?: React.MouseEventHandler<HTMLElement>;
|
||||
onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
|
||||
onSelectorRemove?: (value: DisplayValueType) => void;
|
||||
maxLength?: number;
|
||||
autoFocus?: boolean;
|
||||
/** Check if `tokenSeparators` contains `\n` or `\r\n` */
|
||||
tokenWithEnter?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
focused?: boolean;
|
||||
components: ComponentsConfig;
|
||||
children?: React.ReactElement;
|
||||
}
|
||||
declare const _default: React.ForwardRefExoticComponent<SelectInputProps & React.RefAttributes<SelectInputRef>>;
|
||||
export default _default;
|
||||
+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