From 9bca254f10faf6a9767071dad987b135534c5bea Mon Sep 17 00:00:00 2001 From: Tobias Dillmann Date: Mon, 21 Oct 2024 13:46:54 +0200 Subject: [PATCH] [#53767] ensure focus for autocompleter and multi select --- app/views/filters/_autocomplete.html.erb | 1 + app/views/filters/list/_select.html.erb | 3 ++- .../dynamic/filter/filters-form.controller.ts | 27 +++++++++++++------ spec/features/projects/projects_index_spec.rb | 27 ++++++++++--------- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/app/views/filters/_autocomplete.html.erb b/app/views/filters/_autocomplete.html.erb index 551098352ab3..57a5df7653b7 100644 --- a/app/views/filters/_autocomplete.html.erb +++ b/app/views/filters/_autocomplete.html.erb @@ -13,6 +13,7 @@ # Stimulus controller. }.merge(autocomplete_options.except(:component)), class: 'form--field', + id: "#{filter.name}_value", data: { 'filter-autocomplete': true, 'filter--filters-form-target': 'filterValueContainer', diff --git a/app/views/filters/list/_select.html.erb b/app/views/filters/list/_select.html.erb index 65b73b071b8f..0257656f29a9 100644 --- a/app/views/filters/list/_select.html.erb +++ b/app/views/filters/list/_select.html.erb @@ -9,7 +9,8 @@ { class: 'form--select -slim', 'data-filter--filters-form-target': 'filterValueSelect', - 'data-filter-name': filter.name + 'data-filter-name': filter.name, + id: "#{filter.name}_value" }] if multi_value select_options.third[:multiple] = true diff --git a/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts index ab254b9bcfdd..661600cbf038 100644 --- a/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts @@ -214,16 +214,27 @@ export default class FiltersFormController extends Controller { // Takes an Element and tries to find the next input or select child element. This should be the filter value. // If found, it will be focused. focusFilterValueIfPossible(element:undefined|HTMLElement) { - if (!element) { return; } + if (!element) return; + + // Try different selectors for various filter styles. The order is important as some selectors match unwanted + // hidden fields when used too early in the chain. + const selectors = [ + '.advanced-filters--filter-value ng-select input', + '.advanced-filters--filter-value input', + '.advanced-filters--filter-value select', + ]; - const valueField = element.querySelector('.advanced-filters--filter-value input') as HTMLInputElement; - if (valueField) { - valueField.focus(); - return; - } + selectors.some((selector) => { + const target = element.querySelector(selector) as HTMLElement; - const select = element.querySelector('.advanced-filters--filter-value select') as HTMLSelectElement; - select?.focus(); + if (target) { + target.focus(); + // We have found and focused our element, abort the iteration. + return true; + } + + return false; + }); } removeFilter({ params: { filterName } }:{ params:{ filterName:string } }) { diff --git a/spec/features/projects/projects_index_spec.rb b/spec/features/projects/projects_index_spec.rb index 8598e109ed5e..5e375ac252b8 100644 --- a/spec/features/projects/projects_index_spec.rb +++ b/spec/features/projects/projects_index_spec.rb @@ -877,42 +877,45 @@ def load_and_open_filters(user) # switching to multiselect keeps the current selection cf_filter = page.find("li[data-filter-name='#{list_custom_field.column_name}']") + + select_value_id = "#{list_custom_field.column_name}_value" + within(cf_filter) do # Initial filter is a 'single select' - expect(cf_filter.find(:select, "value")).not_to be_multiple + expect(cf_filter.find(:select, select_value_id)).not_to be_multiple click_on "Toggle multiselect" # switching to multiselect keeps the current selection - expect(cf_filter.find(:select, "value")).to be_multiple - expect(cf_filter).to have_select("value", selected: list_custom_field.possible_values[2].value) + expect(cf_filter.find(:select, select_value_id)).to be_multiple + expect(cf_filter).to have_select(select_value_id, selected: list_custom_field.possible_values[2].value) - select list_custom_field.possible_values[3].value, from: "value" + select list_custom_field.possible_values[3].value, from: select_value_id end wait_for_reload cf_filter = page.find("li[data-filter-name='#{list_custom_field.column_name}']") within(cf_filter) do # Query has two values for that filter, so it should show a 'multi select'. - expect(cf_filter.find(:select, "value")).to be_multiple + expect(cf_filter.find(:select, select_value_id)).to be_multiple expect(cf_filter) - .to have_select("value", + .to have_select(select_value_id, selected: [list_custom_field.possible_values[2].value, list_custom_field.possible_values[3].value]) # switching to single select keeps the first selection - select list_custom_field.possible_values[1].value, from: "value" - unselect list_custom_field.possible_values[2].value, from: "value" + select list_custom_field.possible_values[1].value, from: select_value_id + unselect list_custom_field.possible_values[2].value, from: select_value_id click_on "Toggle multiselect" - expect(cf_filter.find(:select, "value")).not_to be_multiple - expect(cf_filter).to have_select("value", selected: list_custom_field.possible_values[1].value) - expect(cf_filter).to have_no_select("value", selected: list_custom_field.possible_values[3].value) + expect(cf_filter.find(:select, select_value_id)).not_to be_multiple + expect(cf_filter).to have_select(select_value_id, selected: list_custom_field.possible_values[1].value) + expect(cf_filter).to have_no_select(select_value_id, selected: list_custom_field.possible_values[3].value) end wait_for_reload cf_filter = page.find("li[data-filter-name='#{list_custom_field.column_name}']") within(cf_filter) do # Query has one value for that filter, so it should show a 'single select'. - expect(cf_filter.find(:select, "value")).not_to be_multiple + expect(cf_filter.find(:select, select_value_id)).not_to be_multiple end # CF date filter work (at least for one operator)