discourse/spec/system/page_objects/components/d_icon_grid_picker.rb
Régis Hanol 630c295cdb
FIX: Allow typing inside float-kit menus rendered from within a modal (#39370)
`DModal` listens for `keydown` on `documentElement` in the capture phase
and cancels events whose `activeElement` is not inside the modal
wrapper, to prevent keyboard shortcuts from leaking to the page behind
the modal.

Since `DIconGridPicker` replaced the select-kit `IconPicker` in the
custom sidebar section modal (#38943), the picker's filter input renders
inside the `#d-menu-portal-outlet` portal — outside the modal DOM — so
every keystroke was cancelled and typing silently failed.

Allow keydown through when `activeElement` is inside a float-kit portal
(`.fk-d-menu`, `.fk-d-menu-modal`, `.fk-d-tooltip`), which covers any
menu/tooltip opened from within a modal without weakening the leak guard
for elements actually behind the modal.

Also adds a system spec that catches this with real keyboard typing (the
existing `icon_picker.filter` helper used `fill_in` + clicked the target
icon which was already visible without filtering, so the bug slipped
past the suite).

https://meta.discourse.org/t/400945
2026-04-21 15:08:50 +02:00

108 lines
2.3 KiB
Ruby

# frozen_string_literal: true
module PageObjects
module Components
class DIconGridPicker < PageObjects::Components::Base
def initialize(scope = nil)
@scope = scope
end
def expand
trigger.click
self
end
def select_icon(icon_id)
find("[data-icon-id='#{icon_id}']").click
end
def select_first_icon
find("[data-icon-id]", match: :first).click
end
def filter(term)
filter_input.fill_in(with: term)
end
def type_filter(term)
filter_input.send_keys(term)
self
end
def filter_input
find(".d-icon-grid-picker__filter .filter-input")
end
def has_icon?(icon_id)
page.has_css?("[data-icon-id='#{icon_id}']")
end
def clear
scoped(".d-icon-grid-picker__clear").click
end
def value
wrapper["data-value"]
end
def has_selected_icon?(icon_id)
wrapper["data-value"] == icon_id
end
def has_no_selected_icon?
wrapper["data-value"].blank?
end
def has_clear_button?
scoped_has_css?(".d-icon-grid-picker__clear")
end
def has_no_clear_button?
scoped_has_no_css?(".d-icon-grid-picker__clear")
end
private
def wrapper
scoped(".d-icon-grid-picker")
end
def trigger
scoped(".d-icon-grid-picker-trigger")
end
def scoped(selector)
case @scope
when Capybara::Node::Element
@scope.find(selector)
when String
find("#{@scope} #{selector}")
else
find(selector)
end
end
def scoped_has_css?(selector, **opts)
case @scope
when Capybara::Node::Element
@scope.has_css?(selector, **opts)
when String
page.has_css?("#{@scope} #{selector}", **opts)
else
page.has_css?(selector, **opts)
end
end
def scoped_has_no_css?(selector, **opts)
case @scope
when Capybara::Node::Element
@scope.has_no_css?(selector, **opts)
when String
page.has_no_css?("#{@scope} #{selector}", **opts)
else
page.has_no_css?(selector, **opts)
end
end
end
end
end