Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
M
model_analysis
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
model_analysis
Commits
2a9f1577
Commit
2a9f1577
authored
Aug 27, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: stabilize controlled autocomplete selection
parent
082b0507
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
95 additions
and
13 deletions
+95
-13
XinyuanAutocomplete.jsx
src/components/XinyuanAutocomplete.jsx
+95
-13
No files found.
src/components/XinyuanAutocomplete.jsx
View file @
2a9f1577
import
{
DownOutlined
,
SearchOutlined
}
from
'@ant-design/icons'
;
import
{
CheckOutlined
,
DownOutlined
,
SearchOutlined
}
from
'@ant-design/icons'
;
import
{
Autocomplete
,
Label
,
...
...
@@ -17,6 +17,10 @@ function defaultFilter(textValue, inputValue) {
return
!
query
||
String
(
textValue
||
''
).
toLocaleLowerCase
().
includes
(
query
);
}
function
toSelectionKey
(
value
)
{
return
String
(
value
??
''
);
}
export
default
function
XinyuanAutocomplete
({
'aria-label'
:
ariaLabel
,
className
,
...
...
@@ -32,36 +36,95 @@ export default function XinyuanAutocomplete({
placeholder
=
'请选择'
,
searchPlaceholder
=
'输入关键词搜索'
,
selectedKey
=
''
,
selectedKeys
,
selectionMode
=
'single'
,
valueText
,
variant
=
'secondary'
,
})
{
const
normalizedOptions
=
options
.
map
((
option
)
=>
({
id
:
option
.
value
===
''
?
EMPTY_KEY
:
String
(
option
.
value
),
isDisabled
:
Boolean
(
option
.
disabled
),
const
normalizedOptions
=
options
.
map
((
option
)
=>
{
const
optionValue
=
Object
.
prototype
.
hasOwnProperty
.
call
(
option
,
'id'
)
?
option
.
id
:
option
.
value
;
return
{
...
option
,
id
:
optionValue
===
''
?
EMPTY_KEY
:
toSelectionKey
(
optionValue
),
isDisabled
:
Boolean
(
option
.
isDisabled
??
option
.
disabled
),
label
:
String
(
option
.
label
),
}));
};
});
const
normalizedKey
=
selectedKey
===
''
||
selectedKey
==
null
?
EMPTY_KEY
:
String
(
selectedKey
);
const
selectedOption
=
normalizedOptions
.
find
((
option
)
=>
option
.
id
===
normalizedKey
);
:
toSelectionKey
(
selectedKey
);
const
selectedKeySet
=
selectionMode
===
'multiple'
?
new
Set
(
selectedKeys
||
[])
:
new
Set
([
normalizedKey
]);
const
selectedKeyStringSet
=
new
Set
(
Array
.
from
(
selectedKeySet
).
map
(
toSelectionKey
),
);
const
selectedOptions
=
normalizedOptions
.
filter
((
option
)
=>
selectedKeyStringSet
.
has
(
toSelectionKey
(
option
.
id
)),
);
const
resolvedValueText
=
valueText
??
(
selectedOptions
.
length
>
0
?
selectedOptions
.
map
((
option
)
=>
option
.
label
).
join
(
'、'
)
:
placeholder
);
const
rootSelectionProps
=
selectionMode
===
'multiple'
?
{
selectedKeys
:
selectedKeyStringSet
}
:
{
selectedKey
:
normalizedKey
};
const
listBoxSelectionProps
=
selectionMode
===
'multiple'
?
{
selectedKeys
:
selectedKeyStringSet
,
selectionMode
,
}
:
{};
const
handleMultipleOptionPress
=
(
option
)
=>
{
if
(
selectionMode
!==
'multiple'
||
!
onSelectionChange
||
option
?.
isDisabled
||
option
?.
id
===
EMPTY_KEY
)
{
return
;
}
const
optionKey
=
toSelectionKey
(
option
.
id
);
if
(
!
optionKey
)
return
;
const
nextKeys
=
new
Set
(
Array
.
from
(
selectedKeyStringSet
).
filter
(
Boolean
),
);
if
(
nextKeys
.
has
(
optionKey
))
{
nextKeys
.
delete
(
optionKey
);
}
else
{
nextKeys
.
add
(
optionKey
);
}
onSelectionChange
(
nextKeys
);
};
return
(
<
Autocomplete
{
...
rootSelectionProps
}
aria
-
label=
{
ariaLabel
||
label
||
placeholder
}
className=
{
className
}
fullWidth
isDisabled=
{
isDisabled
}
isRequired=
{
isRequired
}
select
edKey=
{
normalizedKey
}
select
ionMode=
{
selectionMode
}
variant=
{
variant
}
onSelectionChange=
{
(
key
)
=>
{
onSelectionChange
?.(
key
===
EMPTY_KEY
?
''
:
String
(
key
??
''
));
onSelectionChange=
{
selectionMode
===
'multiple'
?
onSelectionChange
:
(
key
)
=>
{
onSelectionChange
?.(
key
===
EMPTY_KEY
?
''
:
toSelectionKey
(
key
));
}
}
>
{
label
?
<
Label
>
{
label
}
</
Label
>
:
null
}
<
Autocomplete
.
Trigger
>
<
Autocomplete
.
Value
>
<
span
className=
"block min-w-0 truncate"
>
{
selectedOption
?.
label
||
placeholder
}
{
resolvedValueText
}
</
span
>
</
Autocomplete
.
Value
>
<
Autocomplete
.
Indicator
>
...
...
@@ -84,6 +147,7 @@ export default function XinyuanAutocomplete({
</
SearchField
.
Group
>
</
SearchField
>
<
ListBox
{
...
listBoxSelectionProps
}
aria
-
label=
{
ariaLabel
||
label
||
placeholder
}
items=
{
isLoading
?
[]
:
normalizedOptions
}
renderEmptyState=
{
()
=>
(
...
...
@@ -92,16 +156,34 @@ export default function XinyuanAutocomplete({
</
div
>
)
}
>
{
(
option
)
=>
(
{
(
option
)
=>
{
const
isSelected
=
selectedKeyStringSet
.
has
(
toSelectionKey
(
option
.
id
),
);
return
(
<
ListBox
.
Item
id=
{
option
.
id
}
isDisabled=
{
option
.
isDisabled
}
textValue=
{
option
.
label
}
{
...
(
selectionMode
===
'
multiple
'
?
{
onPress
:
()
=
>
handleMultipleOptionPress(option) }
:
{}
)}
>
<
Label
className=
"truncate"
>
{
option
.
label
}
</
Label
>
{
selectionMode
===
'multiple'
?
(
<
span
aria
-
hidden=
"true"
className=
"ml-auto inline-flex w-5 justify-center text-foreground"
>
{
isSelected
?
<
CheckOutlined
className=
"size-3.5"
/>
:
null
}
</
span
>
)
:
(
<
ListBox
.
ItemIndicator
/>
</
ListBox
.
Item
>
)
}
</
ListBox
.
Item
>
);
}
}
</
ListBox
>
</
Autocomplete
.
Filter
>
</
Autocomplete
.
Popover
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment