From d640ab8428591d0da7544eaeed98af8edf1dc104 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Mon, 15 Jul 2024 11:10:34 +0200 Subject: [PATCH] fix(combobox): dispatch change event on multiple combo changes --- app/javascript/components/ComboBox.tsx | 23 +++++++--- app/javascript/components/react-aria/hooks.ts | 43 +++++++++++++++---- .../champs/multiple_drop_down_list_champ.rb | 2 +- .../multiple_drop_down_list_champ_spec.rb | 6 ++- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/app/javascript/components/ComboBox.tsx b/app/javascript/components/ComboBox.tsx index 63dffa015..10d184497 100644 --- a/app/javascript/components/ComboBox.tsx +++ b/app/javascript/components/ComboBox.tsx @@ -147,6 +147,7 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) { formValue, allowsCustomValue, valueSeparator, + className, ...props } = useMemo(() => s.create(maybeProps, MultiComboBoxProps), [maybeProps]); @@ -174,7 +175,7 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) { const formResetRef = useOnFormReset(onReset); return ( -
+
{selectedItems.length > 0 ? ( @@ -203,16 +204,26 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) { {name ? ( - {hiddenInputValues.map((value, i) => ( + {hiddenInputValues.length == 0 ? ( - ))} + ) : ( + hiddenInputValues.map((value, i) => ( + + )) + )} ) : null}
diff --git a/app/javascript/components/react-aria/hooks.ts b/app/javascript/components/react-aria/hooks.ts index ede4825b6..df940e0f0 100644 --- a/app/javascript/components/react-aria/hooks.ts +++ b/app/javascript/components/react-aria/hooks.ts @@ -23,6 +23,7 @@ export interface ComboBoxProps } const inputMap = new WeakMap(); +const inputCountMap = new WeakMap(); export function useDispatchChangeEvent() { const ref = useRef(null); @@ -30,12 +31,15 @@ export function useDispatchChangeEvent() { ref, dispatch: () => { requestAnimationFrame(() => { - const input = ref.current?.querySelector('input'); - if (input) { - const value = input.value; - const prevValue = inputMap.get(input) || ''; - if (value != prevValue) { - inputMap.set(input, value); + if (ref.current) { + const container = ref.current; + const inputs = Array.from(container.querySelectorAll('input')); + const input = inputs.at(0); + if (input && inputChanged(container, inputs)) { + inputCountMap.set(container, inputs.length); + for (const input of inputs) { + inputMap.set(input, input.value.trim()); + } 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({ defaultItems, defaultSelectedKey, @@ -174,9 +195,13 @@ export function useMultiList({ const filteredItems = useMemo( () => inputValue.length == 0 - ? items - : matchSorter(items, inputValue, { keys: ['label'] }), - [items, inputValue] + ? items.filter((item) => !selectedKeys.has(item.value)) + : matchSorter( + items.filter((item) => !selectedKeys.has(item.value)), + inputValue, + { keys: ['label'] } + ), + [items, inputValue, selectedKeys] ); const selectedItems = useMemo(() => { const selectedItems: Item[] = []; diff --git a/app/models/champs/multiple_drop_down_list_champ.rb b/app/models/champs/multiple_drop_down_list_champ.rb index cd55dbc0c..46c2d5d2d 100644 --- a/app/models/champs/multiple_drop_down_list_champ.rb +++ b/app/models/champs/multiple_drop_down_list_champ.rb @@ -73,7 +73,7 @@ class Champs::MultipleDropDownListChamp < Champ end def value=(value) - return super(nil) if value.nil? + return super(nil) if value.blank? values = if value.is_a?(Array) value diff --git a/spec/models/champs/multiple_drop_down_list_champ_spec.rb b/spec/models/champs/multiple_drop_down_list_champ_spec.rb index 744637431..407347098 100644 --- a/spec/models/champs/multiple_drop_down_list_champ_spec.rb +++ b/spec/models/champs/multiple_drop_down_list_champ_spec.rb @@ -41,8 +41,6 @@ describe Champs::MultipleDropDownListChamp do expect(champ.value).to eq("[\"val1\"]") champ.value = 'val2' expect(champ.value).to eq("[\"val1\",\"val2\"]") - champ.value = '' - expect(champ.value).to eq("[\"val1\",\"val2\"]") champ.value = "[brackets] val4" expect(champ.value).to eq("[\"val1\",\"val2\",\"[brackets] val4\"]") champ.value = nil @@ -51,6 +49,10 @@ describe Champs::MultipleDropDownListChamp do expect(champ.value).to eq("[\"val1\"]") champ.value = [] 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