Commit da2b0019 authored by Andy-bubu's avatar Andy-bubu

fix: stabilize multi-select autocomplete values

parent 84dfb853
import { import {
ChevronDown, ChevronDown,
Check,
Close, Close,
FIELD_SUFFIX_ICON_STYLE, FIELD_SUFFIX_ICON_STYLE,
Search, Search,
...@@ -25,6 +26,10 @@ function getSelectedKeySet(selectionMode, selectedKey, selectedKeys) { ...@@ -25,6 +26,10 @@ function getSelectedKeySet(selectionMode, selectedKey, selectedKeys) {
return new Set(selectedKey === null || selectedKey === undefined ? [] : [selectedKey]); return new Set(selectedKey === null || selectedKey === undefined ? [] : [selectedKey]);
} }
function toSelectionKey(value) {
return String(value ?? '');
}
export default function XinyuanAutocomplete({ export default function XinyuanAutocomplete({
'aria-label': ariaLabel, 'aria-label': ariaLabel,
className, className,
...@@ -59,8 +64,12 @@ export default function XinyuanAutocomplete({ ...@@ -59,8 +64,12 @@ export default function XinyuanAutocomplete({
selectedKey, selectedKey,
selectedKeys, selectedKeys,
); );
const selectedKeyStringSet = new Set(
Array.from(selectedKeySet).map(toSelectionKey),
);
const selectedOptions = options.filter((option) => const selectedOptions = options.filter((option) =>
selectedKeySet.has(option.id), selectedKeySet.has(option.id) ||
selectedKeyStringSet.has(toSelectionKey(option.id)),
); );
const resolvedValueText = const resolvedValueText =
valueText ?? valueText ??
...@@ -69,9 +78,40 @@ export default function XinyuanAutocomplete({ ...@@ -69,9 +78,40 @@ export default function XinyuanAutocomplete({
: placeholder); : placeholder);
const rootSelectionProps = const rootSelectionProps =
selectionMode === 'multiple' selectionMode === 'multiple'
? { selectedKeys } ? { selectedKeys: selectedKeyStringSet }
: { selectedKey }; : { selectedKey };
const listBoxSelectionProps =
selectionMode === 'multiple'
? {
selectionMode,
selectedKeys: selectedKeyStringSet,
}
: {};
const visibleOptions = isLoading || errorText ? [] : options; const visibleOptions = isLoading || errorText ? [] : options;
const handleMultipleOptionPress = (option) => {
if (
selectionMode !== 'multiple' ||
!onSelectionChange ||
option?.isDisabled
) {
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 ( return (
<Autocomplete <Autocomplete
...@@ -125,7 +165,11 @@ export default function XinyuanAutocomplete({ ...@@ -125,7 +165,11 @@ export default function XinyuanAutocomplete({
</SearchField> </SearchField>
<ListBox <ListBox
aria-label={ariaLabel || placeholder} aria-label={ariaLabel || placeholder}
dependencies={[
Array.from(selectedKeyStringSet).sort().join('\u0000'),
]}
items={visibleOptions} items={visibleOptions}
{...listBoxSelectionProps}
renderEmptyState={() => ( renderEmptyState={() => (
<div <div
className="grid min-h-20 place-items-center px-3 py-4" className="grid min-h-20 place-items-center px-3 py-4"
...@@ -135,22 +179,38 @@ export default function XinyuanAutocomplete({ ...@@ -135,22 +179,38 @@ export default function XinyuanAutocomplete({
</div> </div>
)} )}
> >
{(option) => ( {(option) => {
<ListBox.Item const isSelected = selectedKeyStringSet.has(toSelectionKey(option.id));
id={option.id}
isDisabled={option.isDisabled} return (
textValue={option.textValue || option.label} <ListBox.Item
> id={option.id}
{renderOption ? ( isDisabled={option.isDisabled}
renderOption(option) textValue={option.textValue || option.label}
) : ( {...(selectionMode === 'multiple'
<Label className="truncate" title={option.label}> ? { onPress: () => handleMultipleOptionPress(option) }
{option.label} : {})}
</Label> >
)} {renderOption ? (
<ListBox.ItemIndicator /> renderOption(option)
</ListBox.Item> ) : (
)} <Label className="truncate" title={option.label}>
{option.label}
</Label>
)}
{selectionMode === 'multiple' ? (
<span
aria-hidden="true"
className="ml-auto inline-flex w-5 justify-center text-foreground"
>
{isSelected ? <Check size={14} /> : null}
</span>
) : (
<ListBox.ItemIndicator />
)}
</ListBox.Item>
);
}}
</ListBox> </ListBox>
</Autocomplete.Filter> </Autocomplete.Filter>
</Autocomplete.Popover> </Autocomplete.Popover>
......
...@@ -258,6 +258,10 @@ function UserSelector({ value, onChange, multiple, variant }) { ...@@ -258,6 +258,10 @@ function UserSelector({ value, onChange, multiple, variant }) {
function FieldOptionSelect({ multiple, onChange, options, value, variant }) { function FieldOptionSelect({ multiple, onChange, options, value, variant }) {
const values = multiple ? toArray(value).map(String) : [String(value || '')]; const values = multiple ? toArray(value).map(String) : [String(value || '')];
const selectedOptions = options.filter((option) => values.includes(String(option.value))); const selectedOptions = options.filter((option) => values.includes(String(option.value)));
const autocompleteOptions = options.map((option) => ({
id: String(option.value),
label: option.label,
}));
const displayValue = selectedOptions.length const displayValue = selectedOptions.length
? selectedOptions.map((option) => option.label).join('、') ? selectedOptions.map((option) => option.label).join('、')
: '请选择'; : '请选择';
...@@ -265,7 +269,11 @@ function FieldOptionSelect({ multiple, onChange, options, value, variant }) { ...@@ -265,7 +269,11 @@ function FieldOptionSelect({ multiple, onChange, options, value, variant }) {
? { ? {
selectedKeys: new Set(values), selectedKeys: new Set(values),
onSelectionChange: (keys) => onSelectionChange: (keys) =>
onChange(keys === 'all' ? options.map((option) => option.value) : [...keys]), onChange(
keys === 'all'
? autocompleteOptions.map((option) => option.id)
: [...keys],
),
} }
: { : {
selectedKey: values[0] || null, selectedKey: values[0] || null,
...@@ -276,10 +284,7 @@ function FieldOptionSelect({ multiple, onChange, options, value, variant }) { ...@@ -276,10 +284,7 @@ function FieldOptionSelect({ multiple, onChange, options, value, variant }) {
<XinyuanAutocomplete <XinyuanAutocomplete
{...selectProps} {...selectProps}
aria-label="选择字段值" aria-label="选择字段值"
options={options.map((option) => ({ options={autocompleteOptions}
id: String(option.value),
label: option.label,
}))}
placeholder="请选择" placeholder="请选择"
selectionMode={multiple ? 'multiple' : 'single'} selectionMode={multiple ? 'multiple' : 'single'}
valueText={displayValue} valueText={displayValue}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment