From fb07a0ca54ff1772e5cced04e5a15987e862cf6a Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Mon, 23 Sep 2024 15:20:59 +0200 Subject: [PATCH 1/2] fix(combobox): can copy past multiple values in restricted multi select combobox --- .../multiple_drop_down_list_component.rb | 1 + .../column_picker_component.html.haml | 2 +- app/javascript/components/react-aria/hooks.ts | 55 ++++++++++++------- app/javascript/components/react-aria/props.ts | 2 +- spec/system/instructeurs/expert_spec.rb | 7 ++- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/app/components/editable_champ/multiple_drop_down_list_component.rb b/app/components/editable_champ/multiple_drop_down_list_component.rb index 6428d70df..0269ca782 100644 --- a/app/components/editable_champ/multiple_drop_down_list_component.rb +++ b/app/components/editable_champ/multiple_drop_down_list_component.rb @@ -17,6 +17,7 @@ class EditableChamp::MultipleDropDownListComponent < EditableChamp::EditableCham name: @form.field_name(:value, multiple: true), selected_keys: @champ.selected_options, items: @champ.enabled_non_empty_options, + value_separator: false, 'aria-label': @champ.libelle, 'aria-describedby': @champ.describedby_id, 'aria-labelledby': @champ.labelledby_id) diff --git a/app/components/instructeurs/column_picker_component/column_picker_component.html.haml b/app/components/instructeurs/column_picker_component/column_picker_component.html.haml index 74a31729e..a5953d0be 100644 --- a/app/components/instructeurs/column_picker_component/column_picker_component.html.haml +++ b/app/components/instructeurs/column_picker_component/column_picker_component.html.haml @@ -1,5 +1,5 @@ = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do %react-fragment - = render ReactComponent.new "ComboBox/MultiComboBox", items: @displayable_columns_for_select, selected_keys: @displayable_columns_selected, name: 'values[]', 'aria-label': 'Colonne à afficher' + = render ReactComponent.new "ComboBox/MultiComboBox", items: @displayable_columns_for_select, selected_keys: @displayable_columns_selected, name: 'values[]', 'aria-label': 'Colonne à afficher', value_separator: false = submit_tag t('.save'), class: 'fr-btn fr-btn--secondary' diff --git a/app/javascript/components/react-aria/hooks.ts b/app/javascript/components/react-aria/hooks.ts index df940e0f0..5df65dda7 100644 --- a/app/javascript/components/react-aria/hooks.ts +++ b/app/javascript/components/react-aria/hooks.ts @@ -168,13 +168,18 @@ export function useMultiList({ defaultItems?: Item[]; defaultSelectedKeys?: string[]; allowsCustomValue?: boolean; - valueSeparator?: string; + valueSeparator?: string | false; onChange?: () => void; focusInput?: () => void; formValue?: 'text' | 'key'; }) { const valueSeparatorRegExp = useMemo( - () => (valueSeparator ? new RegExp(valueSeparator) : /\s|,|;/), + () => + valueSeparator === false + ? false + : valueSeparator + ? new RegExp(valueSeparator) + : /\s|,|;/, [valueSeparator] ); const [selectedKeys, setSelectedKeys] = useState( @@ -219,7 +224,7 @@ export function useMultiList({ const values = selectedItems.map((item) => formValue == 'text' || allowsCustomValue ? item.label : item.value ); - if (!allowsCustomValue || inputValue == '') { + if (!valueSeparatorRegExp || !allowsCustomValue || inputValue == '') { return values; } return [ @@ -269,26 +274,34 @@ export function useMultiList({ setInputValue(''); return; } - if (allowsCustomValue) { - const values = value.split(valueSeparatorRegExp); - // if input contains a separator, add all values - if (values.length > 1) { - const addedKeys = values.filter(Boolean); - setSelectedKeys((keys) => { - const selectedKeys = new Set(keys.values()); - for (const key of addedKeys) { - selectedKeys.add(key); - } - return selectedKeys; - }); - setInputValue(''); - } else { - setInputValue(value); - } - onChange?.(); - } else { + + if (!valueSeparatorRegExp) { setInputValue(value); + return; } + + const values = value.split(valueSeparatorRegExp); + if (values.length < 2) { + setInputValue(value); + return; + } + + // if input contains a separator, add all values + const addedKeys = allowsCustomValue + ? values.filter(Boolean) + : values + .filter(Boolean) + .map((value) => items.find((item) => item.label == value)?.value) + .filter((key) => key != null); + setSelectedKeys((keys) => { + const selectedKeys = new Set(keys.values()); + for (const key of addedKeys) { + selectedKeys.add(key); + } + return selectedKeys; + }); + onChange?.(); + setInputValue(''); } ); diff --git a/app/javascript/components/react-aria/props.ts b/app/javascript/components/react-aria/props.ts index 10d62859a..0b4347de5 100644 --- a/app/javascript/components/react-aria/props.ts +++ b/app/javascript/components/react-aria/props.ts @@ -56,7 +56,7 @@ export const MultiComboBoxProps = s.assign( s.object({ selectedKeys: s.array(s.string()), allowsCustomValue: s.boolean(), - valueSeparator: s.string() + valueSeparator: s.union([s.string(), s.literal(false)]) }) ) ); diff --git a/spec/system/instructeurs/expert_spec.rb b/spec/system/instructeurs/expert_spec.rb index c1e70385a..eb8e7b78f 100644 --- a/spec/system/instructeurs/expert_spec.rb +++ b/spec/system/instructeurs/expert_spec.rb @@ -7,6 +7,8 @@ describe 'Inviting an expert:', js: true do let(:instructeur) { create(:instructeur, password: SECURE_PASSWORD) } let(:expert) { create(:expert, password: expert_password) } let(:expert2) { create(:expert, password: expert_password) } + let(:expert3) { create(:expert, password: expert_password) } + let(:expert4) { create(:expert, password: expert_password) } let(:expert_password) { 'mot de passe d’expert' } let(:procedure) { create(:procedure, :published, instructeurs: [instructeur], types_de_champ_public: [{ type: :dossier_link }]) } let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure:) } @@ -31,6 +33,7 @@ describe 'Inviting an expert:', js: true do within('.fr-sidemenu') { click_on 'Demander un avis' } expect(page).to have_current_path(avis_new_instructeur_dossier_path(procedure, dossier)) + fill_in 'Emails', with: "#{expert3.email},#{expert4.email}" fill_in 'Emails', with: "#{expert.email}," fill_in 'Emails', with: expert2.email fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.' @@ -45,10 +48,12 @@ describe 'Inviting an expert:', js: true do within('section') do expect(page).to have_content(expert.email.to_s) expect(page).to have_content(expert2.email.to_s) + expect(page).to have_content(expert3.email.to_s) + expect(page).to have_content(expert4.email.to_s) expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.') end - expect(Avis.count).to eq(4) + expect(Avis.count).to eq(8) expect(emails_sent_to(expert.email.to_s).size).to eq(1) expect(emails_sent_to(expert2.email.to_s).size).to eq(1) invitation_email = open_email(expert.email.to_s) From 3e9a51d9b8f17f40e73a329043dd11e6560418c3 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Mon, 23 Sep 2024 15:29:10 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(combobox):=20don=E2=80=99t=20output=20u?= =?UTF-8?q?ndefined=20className?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/components/ComboBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/components/ComboBox.tsx b/app/javascript/components/ComboBox.tsx index 75a160df4..d43843255 100644 --- a/app/javascript/components/ComboBox.tsx +++ b/app/javascript/components/ComboBox.tsx @@ -179,7 +179,7 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) { const formResetRef = useOnFormReset(onReset); return ( -
+
{selectedItems.length > 0 ? (