From 2fabc2a1d7de4023efec03df55a6fbb889a92ab3 Mon Sep 17 00:00:00 2001 From: Test Date: Fri, 10 Apr 2026 11:53:07 +0200 Subject: [PATCH] fix: restore filter field dropdown scrolling --- src/components/FilterFieldCombobox.tsx | 106 +++++++++++++----- .../FilterFieldCombobox.test.tsx | 37 ++++++ 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/src/components/FilterFieldCombobox.tsx b/src/components/FilterFieldCombobox.tsx index 049a3fbc..bd5e7a91 100644 --- a/src/components/FilterFieldCombobox.tsx +++ b/src/components/FilterFieldCombobox.tsx @@ -54,6 +54,62 @@ function stepHighlightedIndex(current: number, optionCount: number, direction: ' return (current - 1 + optionCount) % optionCount } +function moveHighlightedOption({ + event, + open, + options, + direction, + openCombobox, + setHighlightedIndex, +}: { + event: KeyboardEvent + open: boolean + options: string[] + direction: 'next' | 'previous' + openCombobox: () => void + setHighlightedIndex: (updater: number | ((current: number) => number)) => void +}) { + event.preventDefault() + if (!open) { + openCombobox() + return + } + if (options.length === 0) return + setHighlightedIndex((current) => stepHighlightedIndex(current, options.length, direction)) +} + +function selectHighlightedOption({ + event, + open, + options, + highlightedIndex, + selectOption, +}: { + event: KeyboardEvent + open: boolean + options: string[] + highlightedIndex: number + selectOption: (value: string) => void +}) { + if (!open || highlightedIndex < 0 || options[highlightedIndex] === undefined) return + event.preventDefault() + selectOption(options[highlightedIndex]) +} + +function closeOpenCombobox({ + event, + open, + closeCombobox, +}: { + event: KeyboardEvent + open: boolean + closeCombobox: () => void +}) { + if (!open) return + event.preventDefault() + closeCombobox() +} + function handleFilterFieldKeyDown({ event, open, @@ -75,32 +131,16 @@ function handleFilterFieldKeyDown({ }) { switch (event.key) { case 'ArrowDown': - event.preventDefault() - if (!open) { - openCombobox() - return - } - if (options.length === 0) return - setHighlightedIndex((current) => stepHighlightedIndex(current, options.length, 'next')) + moveHighlightedOption({ event, open, options, direction: 'next', openCombobox, setHighlightedIndex }) return case 'ArrowUp': - event.preventDefault() - if (!open) { - openCombobox() - return - } - if (options.length === 0) return - setHighlightedIndex((current) => stepHighlightedIndex(current, options.length, 'previous')) + moveHighlightedOption({ event, open, options, direction: 'previous', openCombobox, setHighlightedIndex }) return case 'Enter': - if (!open || highlightedIndex < 0 || options[highlightedIndex] === undefined) return - event.preventDefault() - selectOption(options[highlightedIndex]) + selectHighlightedOption({ event, open, options, highlightedIndex, selectOption }) return case 'Escape': - if (!open) return - event.preventDefault() - closeCombobox() + closeOpenCombobox({ event, open, closeCombobox }) return default: return @@ -178,21 +218,27 @@ function FilterFieldPopoverPanel({ event.preventDefault()} onCloseAutoFocus={(event) => event.preventDefault()} data-testid="filter-field-combobox-popover" > -
- +
event.stopPropagation()} + > +
+ +
) diff --git a/src/components/filter-builder/FilterFieldCombobox.test.tsx b/src/components/filter-builder/FilterFieldCombobox.test.tsx index 1db0a41e..c2393e81 100644 --- a/src/components/filter-builder/FilterFieldCombobox.test.tsx +++ b/src/components/filter-builder/FilterFieldCombobox.test.tsx @@ -19,4 +19,41 @@ describe('FilterFieldCombobox', () => { expect(listbox).toBeInTheDocument() expect(root.contains(listbox)).toBe(false) }) + + it('renders the option list inside a dedicated scroll container', () => { + render( + `Field ${index + 1}`)} + onChange={vi.fn()} + />, + ) + + fireEvent.focus(screen.getByTestId('filter-field-combobox-input')) + + expect(screen.getByTestId('filter-field-combobox-scroll-area')).toHaveClass( + 'max-h-60', + 'overflow-y-auto', + 'overscroll-contain', + ) + }) + + it('stops wheel events from bubbling out of the scroll container', () => { + const onWheel = vi.fn() + + render( +
+ `Field ${index + 1}`)} + onChange={vi.fn()} + /> +
, + ) + + fireEvent.focus(screen.getByTestId('filter-field-combobox-input')) + fireEvent.wheel(screen.getByTestId('filter-field-combobox-scroll-area')) + + expect(onWheel).not.toHaveBeenCalled() + }) })