diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx index 3457d9a2..665d480c 100644 --- a/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx +++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx @@ -1,4 +1,5 @@ -import { render } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; import DataViewTextFilter, { DataViewTextFilterProps } from './DataViewTextFilter'; import DataViewToolbar from '../DataViewToolbar'; @@ -20,4 +21,87 @@ describe('DataViewTextFilter component', () => { />); expect(container).toMatchSnapshot(); }); + + it('should focus the search input when "/" key is pressed and filter is visible', () => { + const { container } = render( + } + />); + + const input = document.getElementById('test-filter') as HTMLInputElement; + expect(input).toBeInTheDocument(); + + // Simulate pressing "/" key by creating and dispatching a KeyboardEvent + const keyEvent = new KeyboardEvent('keydown', { + key: '/', + code: 'Slash', + bubbles: true, + cancelable: true, + }); + window.dispatchEvent(keyEvent); + + // Check that the input has focus + expect(document.activeElement).toBe(input); + }); + + it('should not focus the search input when "/" key is pressed if filter is not visible', () => { + const { container } = render( + } + />); + + const input = document.getElementById('test-filter') as HTMLInputElement; + + // Simulate pressing "/" key + const keyEvent = new KeyboardEvent('keydown', { + key: '/', + code: 'Slash', + bubbles: true, + cancelable: true, + }); + window.dispatchEvent(keyEvent); + + if (input) { + expect(document.activeElement).not.toBe(input); + } + }); + + it('should not focus the search input when "/" key is pressed while typing in another input', () => { + const { container } = render( +
+ + + } + /> +
+ ); + + const otherInput = container.querySelector('[data-testid="other-input"]') as HTMLInputElement; + const searchInput = document.getElementById('test-filter') as HTMLInputElement; + + // Focus the other input first + otherInput.focus(); + expect(document.activeElement).toBe(otherInput); + + // Simulate pressing "/" key while focused on the other input + // The event target should be the input element + const keyEvent = new KeyboardEvent('keydown', { + key: '/', + code: 'Slash', + bubbles: true, + cancelable: true, + }); + Object.defineProperty(keyEvent, 'target', { + value: otherInput, + enumerable: true, + }); + window.dispatchEvent(keyEvent); + + // The search input should not be focused since we're already in an input field + expect(document.activeElement).toBe(otherInput); + }); }); diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx index 9d30ff31..2e1696cd 100644 --- a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx +++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { FC, useEffect } from 'react'; import { SearchInput, SearchInputProps, ToolbarFilter, ToolbarFilterProps } from '@patternfly/react-core'; /** extends SearchInputProps */ @@ -29,26 +29,55 @@ export const DataViewTextFilter: FC = ({ trimValue = true, ouiaId = 'DataViewTextFilter', ...props -}: DataViewTextFilterProps) => ( - 0 ? [ { key: title, node: value } ] : []} - deleteLabel={() => onChange?.(undefined, '')} - categoryName={title} - showToolbarItem={showToolbarItem} - > - onChange?.(e, trimValue ? inputValue.trim() : inputValue)} - onClear={onClear} - placeholder={`Filter by ${title}`} - aria-label={`${title ?? filterId} filter`} - data-ouia-component-id={`${ouiaId}-input`} - {...props} - /> - -); +}: DataViewTextFilterProps) => { + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // Only handle "/" key when not typing in an input, textarea, or contenteditable element + if (event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey) { + const target = event.target as HTMLElement; + const isInputElement = target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.isContentEditable; + + // Only focus if the filter is visible and we're not already in an input field + if (showToolbarItem && !isInputElement) { + // Find the input element by its ID (searchInputId prop) + const inputElement = document.getElementById(filterId) as HTMLInputElement; + if (inputElement) { + event.preventDefault(); + inputElement.focus(); + } + } + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [showToolbarItem, filterId]); + + return ( + 0 ? [ { key: title, node: value } ] : []} + deleteLabel={() => onChange?.(undefined, '')} + categoryName={title} + showToolbarItem={showToolbarItem} + > + onChange?.(e, trimValue ? inputValue.trim() : inputValue)} + onClear={onClear} + placeholder={`Filter by ${title}`} + aria-label={`${title ?? filterId} filter`} + data-ouia-component-id={`${ouiaId}-input`} + {...props} + /> + + ); +}; export default DataViewTextFilter;