@@ -58,15 +58,17 @@ export default function useSingle(props: SelectInputProps) {
5858 const blurTimeoutRef = useRef ( null ) ;
5959 const customElementRef = useRef < HTMLSpanElement > ( null ) ;
6060
61+ // 以下三个状态仅在 allowInput=true 时有意义
6162 const [ isTyping , setIsTyping ] = useState < boolean > ( false ) ;
6263 const [ labelWidth , setLabelWidth ] = useState < number > ( 0 ) ;
6364 const [ suffixSpace , setSuffixSpace ] = useState < number > ( 0 ) ;
6465
6566 const singleValueDisplay = useMemo ( ( ) => valueDisplay ?? getOptionLabel ( value , keys ) , [ value , valueDisplay , keys ] ) ;
6667
68+ // allowInput=true 时 DOM 结构采用 absolute 覆盖层实现
6769 const showCustomElement = useMemo (
68- ( ) => ! isTyping && ! inputValue && React . isValidElement ( singleValueDisplay ) ,
69- [ isTyping , inputValue , singleValueDisplay ] ,
70+ ( ) => allowInput && ! isTyping && ! inputValue && React . isValidElement ( singleValueDisplay ) ,
71+ [ allowInput , isTyping , inputValue , singleValueDisplay ] ,
7072 ) ;
7173
7274 const onInnerClear = ( context : { e : React . MouseEvent < SVGSVGElement > } ) => {
@@ -82,14 +84,16 @@ export default function useSingle(props: SelectInputProps) {
8284 } ;
8385
8486 useEffect ( ( ) => {
87+ if ( ! allowInput ) return ;
8588 const labelEl = inputRef . current ?. currentElement . querySelector ( `.${ classPrefix } -input__prefix` ) ;
8689 if ( labelEl ) {
8790 const prefixWidth = labelEl . getBoundingClientRect ( ) . width ;
8891 setLabelWidth ( prefixWidth ) ;
8992 }
90- } , [ label , classPrefix ] ) ;
93+ } , [ allowInput , label , classPrefix ] ) ;
9194
9295 useEffect ( ( ) => {
96+ if ( ! allowInput ) return ;
9397 const inputEl = inputRef . current ?. inputElement ;
9498 if ( ! inputEl ) return ;
9599 // autoWidth 且存在自定义元素时需要撑开宽度
@@ -98,32 +102,16 @@ export default function useSingle(props: SelectInputProps) {
98102 return ;
99103 }
100104 const el = customElementRef . current ;
101-
102- const measure = ( ) => {
103- // 测量真实内容宽度时,临时强制 nowrap,避免被父级容器(受 suffixSpace 影响)压缩换行导致测量值偏小
104- const prevWhiteSpace = el . style . whiteSpace ;
105- el . style . whiteSpace = 'nowrap' ;
106- const { width } = el . getBoundingClientRect ( ) ;
107- el . style . whiteSpace = prevWhiteSpace ;
108- inputEl . style . minWidth = width > 0 ? `${ width } px` : '' ;
109- } ;
110-
111- measure ( ) ;
112-
113- let ro : ResizeObserver | null = null ;
114- if ( typeof ResizeObserver !== 'undefined' ) {
115- ro = new ResizeObserver ( ( ) => {
116- measure ( ) ;
117- } ) ;
118- ro . observe ( el ) ;
119- }
120-
121- return ( ) => {
122- ro ?. disconnect ( ) ;
123- } ;
124- } , [ autoWidth , showCustomElement , singleValueDisplay ] ) ;
105+ // 测量真实内容宽度时,临时强制 nowrap,避免被父级容器(受 suffixSpace 影响)压缩换行导致测量值偏小
106+ const prevWhiteSpace = el . style . whiteSpace ;
107+ el . style . whiteSpace = 'nowrap' ;
108+ const { width } = el . getBoundingClientRect ( ) ;
109+ el . style . whiteSpace = prevWhiteSpace ;
110+ inputEl . style . minWidth = width > 0 ? `${ width } px` : '' ;
111+ } , [ allowInput , autoWidth , showCustomElement , singleValueDisplay ] ) ;
125112
126113 useEffect ( ( ) => {
114+ if ( ! allowInput ) return ;
127115 // 避免内容延伸盖到右侧的 suffixIcon 区域,需要测量 input 右侧到 wrapper 右侧的距离作为 right 留白
128116 if ( ! showCustomElement ) {
129117 setSuffixSpace ( 0 ) ;
@@ -149,7 +137,7 @@ export default function useSingle(props: SelectInputProps) {
149137 wrapperEl . removeEventListener ( 'mouseenter' , measure ) ;
150138 wrapperEl . removeEventListener ( 'mouseleave' , measure ) ;
151139 } ;
152- } , [ showCustomElement , singleValueDisplay , clearable , suffixIcon , props . suffix ] ) ;
140+ } , [ allowInput , showCustomElement , singleValueDisplay , clearable , suffixIcon , props . suffix ] ) ;
153141
154142 const renderSelectSingle = (
155143 popupVisible : boolean ,
@@ -182,7 +170,54 @@ export default function useSingle(props: SelectInputProps) {
182170 // !popupVisible && setInputValue(getInputValue(value, keys), { ...context, trigger: 'input' });
183171 } ;
184172
173+ const sharedInputProps = {
174+ ref : inputRef ,
175+ ...commonInputProps ,
176+ autocomplete : 'off' as const ,
177+ autoWidth,
178+ style : inputProps ?. style ,
179+ onChange : onInnerInputChange ,
180+ onClear : onInnerClear ,
181+ onFocus : handleFocus ,
182+ onEnter : ( val , context ) => {
183+ props . onEnter ?.( value , { ...context , inputValue : val } ) ;
184+ } ,
185+ onBlur : handleBlur ,
186+ ...inputProps ,
187+ inputClass : classNames ( inputProps ?. inputClass , {
188+ [ `${ classPrefix } -input--focused` ] : popupVisible ,
189+ [ `${ classPrefix } -is-focused` ] : popupVisible ,
190+ } ) ,
191+ } ;
192+
193+ // allowInput=false 且 singleValueDisplay 是 React 元素,直接放进 label,不需要 absolute
194+ // (历史实现,避免 DOM 结构变更,保留原有逻辑,大版本可考虑彻底统一)
195+ const isStaticCustomElement = ! allowInput && React . isValidElement ( singleValueDisplay ) ;
196+
197+ if ( isStaticCustomElement ) {
198+ return (
199+ < Input
200+ { ...sharedInputProps }
201+ showClearIconOnEmpty = { clearable }
202+ allowInput = { false }
203+ label = {
204+ ( label || singleValueDisplay ) && (
205+ < >
206+ { label }
207+ { singleValueDisplay as React . ReactNode }
208+ </ >
209+ )
210+ }
211+ value = " "
212+ placeholder = ""
213+ />
214+ ) ;
215+ }
216+
185217 const displayedValue = ( ) : string => {
218+ if ( inputProps ?. value !== undefined ) {
219+ return inputProps . value ;
220+ }
186221 if ( popupVisible && inputValue ) {
187222 return inputValue ;
188223 }
@@ -200,7 +235,7 @@ export default function useSingle(props: SelectInputProps) {
200235 return singleValueDisplay ;
201236 }
202237 if ( showCustomElement ) return '' ;
203- return props . placeholder ;
238+ return inputProps ?. placeholder ?? props . placeholder ;
204239 } ;
205240
206241 const labelNode = showCustomElement ? (
@@ -233,11 +268,9 @@ export default function useSingle(props: SelectInputProps) {
233268
234269 return (
235270 < Input
236- ref = { inputRef }
271+ { ... sharedInputProps }
237272 // 当 valueDisplay 为 自定义元素时,选中内容时 input 依旧为空,确保此时 clear icon 可见
238273 showClearIconOnEmpty = { clearable && showCustomElement }
239- { ...commonInputProps }
240- autocomplete = "off"
241274 suffix = {
242275 labelNode ||
243276 ( commonInputProps . suffix && (
@@ -247,22 +280,10 @@ export default function useSingle(props: SelectInputProps) {
247280 </ >
248281 ) )
249282 }
250- autoWidth = { autoWidth }
251- style = { inputProps ?. style }
252283 allowInput = { allowInput }
253284 label = { label }
254285 value = { displayedValue ( ) }
255286 placeholder = { displayedPlaceholder ( ) }
256- onChange = { onInnerInputChange }
257- onClear = { onInnerClear }
258- // [Important Info]: SelectInput.blur is not equal to Input, example: click popup panel
259- onFocus = { handleFocus }
260- onEnter = { ( val , context ) => {
261- props . onEnter ?.( value , { ...context , inputValue : val } ) ;
262- } }
263- // onBlur need to triggered by input when popup panel is null or when popupVisible is forced to false
264- onBlur = { handleBlur }
265- { ...inputProps }
266287 onCompositionstart = { ( v , ctx ) => {
267288 setIsTyping ( true ) ;
268289 inputProps ?. onCompositionstart ?.( v , ctx ) ;
@@ -271,10 +292,6 @@ export default function useSingle(props: SelectInputProps) {
271292 setIsTyping ( false ) ;
272293 inputProps ?. onCompositionend ?.( v , ctx ) ;
273294 } }
274- inputClass = { classNames ( inputProps ?. inputClass , {
275- [ `${ classPrefix } -input--focused` ] : popupVisible ,
276- [ `${ classPrefix } -is-focused` ] : popupVisible ,
277- } ) }
278295 />
279296 ) ;
280297 } ;
0 commit comments