fix(combobox): dispatch change event on multiple combo changes

This commit is contained in:
Paul Chavard 2024-07-15 11:10:34 +02:00
parent 0b6fefa582
commit d640ab8428
No known key found for this signature in database
4 changed files with 56 additions and 18 deletions

View file

@ -147,6 +147,7 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) {
formValue, formValue,
allowsCustomValue, allowsCustomValue,
valueSeparator, valueSeparator,
className,
...props ...props
} = useMemo(() => s.create(maybeProps, MultiComboBoxProps), [maybeProps]); } = useMemo(() => s.create(maybeProps, MultiComboBoxProps), [maybeProps]);
@ -174,7 +175,7 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) {
const formResetRef = useOnFormReset(onReset); const formResetRef = useOnFormReset(onReset);
return ( return (
<div className="fr-ds-combobox__multiple"> <div className={`fr-ds-combobox__multiple ${className}`}>
{selectedItems.length > 0 ? ( {selectedItems.length > 0 ? (
<TagGroup onRemove={onRemove} aria-label={props['aria-label']}> <TagGroup onRemove={onRemove} aria-label={props['aria-label']}>
<TagList items={selectedItems} className="fr-tag-list"> <TagList items={selectedItems} className="fr-tag-list">
@ -203,7 +204,16 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) {
</ComboBox> </ComboBox>
{name ? ( {name ? (
<span ref={ref}> <span ref={ref}>
{hiddenInputValues.map((value, i) => ( {hiddenInputValues.length == 0 ? (
<input
type="hidden"
value=""
name={name}
form={form}
ref={formResetRef}
/>
) : (
hiddenInputValues.map((value, i) => (
<input <input
type="hidden" type="hidden"
value={value} value={value}
@ -212,7 +222,8 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) {
ref={i == 0 ? formResetRef : undefined} ref={i == 0 ? formResetRef : undefined}
key={value} key={value}
/> />
))} ))
)}
</span> </span>
) : null} ) : null}
</div> </div>

View file

@ -23,6 +23,7 @@ export interface ComboBoxProps
} }
const inputMap = new WeakMap<HTMLInputElement, string>(); const inputMap = new WeakMap<HTMLInputElement, string>();
const inputCountMap = new WeakMap<HTMLSpanElement, number>();
export function useDispatchChangeEvent() { export function useDispatchChangeEvent() {
const ref = useRef<HTMLSpanElement>(null); const ref = useRef<HTMLSpanElement>(null);
@ -30,12 +31,15 @@ export function useDispatchChangeEvent() {
ref, ref,
dispatch: () => { dispatch: () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
const input = ref.current?.querySelector('input'); if (ref.current) {
if (input) { const container = ref.current;
const value = input.value; const inputs = Array.from(container.querySelectorAll('input'));
const prevValue = inputMap.get(input) || ''; const input = inputs.at(0);
if (value != prevValue) { if (input && inputChanged(container, inputs)) {
inputMap.set(input, value); inputCountMap.set(container, inputs.length);
for (const input of inputs) {
inputMap.set(input, input.value.trim());
}
input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true }));
} }
} }
@ -44,6 +48,23 @@ export function useDispatchChangeEvent() {
}; };
} }
// I am not proude of this code. We have to tack values and number of values to deal with multi select combobox.
// I have a plan to remove this code. Soon.
function inputChanged(container: HTMLSpanElement, inputs: HTMLInputElement[]) {
const prevCount = inputCountMap.get(container) ?? 0;
if (prevCount != inputs.length) {
return true;
}
for (const input of inputs) {
const value = input.value.trim();
const prevValue = inputMap.get(input);
if (prevValue == null || prevValue != value) {
return true;
}
}
return false;
}
export function useSingleList({ export function useSingleList({
defaultItems, defaultItems,
defaultSelectedKey, defaultSelectedKey,
@ -174,9 +195,13 @@ export function useMultiList({
const filteredItems = useMemo( const filteredItems = useMemo(
() => () =>
inputValue.length == 0 inputValue.length == 0
? items ? items.filter((item) => !selectedKeys.has(item.value))
: matchSorter(items, inputValue, { keys: ['label'] }), : matchSorter(
[items, inputValue] items.filter((item) => !selectedKeys.has(item.value)),
inputValue,
{ keys: ['label'] }
),
[items, inputValue, selectedKeys]
); );
const selectedItems = useMemo(() => { const selectedItems = useMemo(() => {
const selectedItems: Item[] = []; const selectedItems: Item[] = [];

View file

@ -73,7 +73,7 @@ class Champs::MultipleDropDownListChamp < Champ
end end
def value=(value) def value=(value)
return super(nil) if value.nil? return super(nil) if value.blank?
values = if value.is_a?(Array) values = if value.is_a?(Array)
value value

View file

@ -41,8 +41,6 @@ describe Champs::MultipleDropDownListChamp do
expect(champ.value).to eq("[\"val1\"]") expect(champ.value).to eq("[\"val1\"]")
champ.value = 'val2' champ.value = 'val2'
expect(champ.value).to eq("[\"val1\",\"val2\"]") expect(champ.value).to eq("[\"val1\",\"val2\"]")
champ.value = ''
expect(champ.value).to eq("[\"val1\",\"val2\"]")
champ.value = "[brackets] val4" champ.value = "[brackets] val4"
expect(champ.value).to eq("[\"val1\",\"val2\",\"[brackets] val4\"]") expect(champ.value).to eq("[\"val1\",\"val2\",\"[brackets] val4\"]")
champ.value = nil champ.value = nil
@ -51,6 +49,10 @@ describe Champs::MultipleDropDownListChamp do
expect(champ.value).to eq("[\"val1\"]") expect(champ.value).to eq("[\"val1\"]")
champ.value = [] champ.value = []
expect(champ.value).to be_nil expect(champ.value).to be_nil
champ.value = ["val1"]
expect(champ.value).to eq("[\"val1\"]")
champ.value = ''
expect(champ.value).to be_nil
} }
end end
end end