Skip to content

Commit 5db6ac1

Browse files
committed
fix: handle keyboard shortcut for text filter
1 parent 12cdf95 commit 5db6ac1

2 files changed

Lines changed: 136 additions & 23 deletions

File tree

packages/module/src/DataViewTextFilter/DataViewTextFilter.test.tsx

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { render } from '@testing-library/react';
1+
import { render, fireEvent } from '@testing-library/react';
2+
import '@testing-library/jest-dom';
23
import DataViewTextFilter, { DataViewTextFilterProps } from './DataViewTextFilter';
34
import DataViewToolbar from '../DataViewToolbar';
45

@@ -20,4 +21,87 @@ describe('DataViewTextFilter component', () => {
2021
/>);
2122
expect(container).toMatchSnapshot();
2223
});
24+
25+
it('should focus the search input when "/" key is pressed and filter is visible', () => {
26+
const { container } = render(<DataViewToolbar
27+
filters={
28+
<DataViewTextFilter {...defaultProps} showToolbarItem={true} />
29+
}
30+
/>);
31+
32+
const input = document.getElementById('test-filter') as HTMLInputElement;
33+
expect(input).toBeInTheDocument();
34+
35+
// Simulate pressing "/" key by creating and dispatching a KeyboardEvent
36+
const keyEvent = new KeyboardEvent('keydown', {
37+
key: '/',
38+
code: 'Slash',
39+
bubbles: true,
40+
cancelable: true,
41+
});
42+
window.dispatchEvent(keyEvent);
43+
44+
// Check that the input has focus
45+
expect(document.activeElement).toBe(input);
46+
});
47+
48+
it('should not focus the search input when "/" key is pressed if filter is not visible', () => {
49+
const { container } = render(<DataViewToolbar
50+
filters={
51+
<DataViewTextFilter {...defaultProps} showToolbarItem={false} />
52+
}
53+
/>);
54+
55+
const input = document.getElementById('test-filter') as HTMLInputElement;
56+
57+
// Simulate pressing "/" key
58+
const keyEvent = new KeyboardEvent('keydown', {
59+
key: '/',
60+
code: 'Slash',
61+
bubbles: true,
62+
cancelable: true,
63+
});
64+
window.dispatchEvent(keyEvent);
65+
66+
if (input) {
67+
expect(document.activeElement).not.toBe(input);
68+
}
69+
});
70+
71+
it('should not focus the search input when "/" key is pressed while typing in another input', () => {
72+
const { container } = render(
73+
<div>
74+
<input data-testid="other-input" />
75+
<DataViewToolbar
76+
filters={
77+
<DataViewTextFilter {...defaultProps} showToolbarItem={true} />
78+
}
79+
/>
80+
</div>
81+
);
82+
83+
const otherInput = container.querySelector('[data-testid="other-input"]') as HTMLInputElement;
84+
const searchInput = document.getElementById('test-filter') as HTMLInputElement;
85+
86+
// Focus the other input first
87+
otherInput.focus();
88+
expect(document.activeElement).toBe(otherInput);
89+
90+
// Simulate pressing "/" key while focused on the other input
91+
// The event target should be the input element
92+
const keyEvent = new KeyboardEvent('keydown', {
93+
key: '/',
94+
code: 'Slash',
95+
bubbles: true,
96+
cancelable: true,
97+
});
98+
Object.defineProperty(keyEvent, 'target', {
99+
value: otherInput,
100+
enumerable: true,
101+
});
102+
window.dispatchEvent(keyEvent);
103+
104+
// The search input should not be focused since we're already in an input field
105+
expect(document.activeElement).toBe(otherInput);
106+
});
23107
});

packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC } from 'react';
1+
import { FC, useEffect } from 'react';
22
import { SearchInput, SearchInputProps, ToolbarFilter, ToolbarFilterProps } from '@patternfly/react-core';
33

44
/** extends SearchInputProps */
@@ -29,26 +29,55 @@ export const DataViewTextFilter: FC<DataViewTextFilterProps> = ({
2929
trimValue = true,
3030
ouiaId = 'DataViewTextFilter',
3131
...props
32-
}: DataViewTextFilterProps) => (
33-
<ToolbarFilter
34-
key={ouiaId}
35-
data-ouia-component-id={ouiaId}
36-
labels={value.length > 0 ? [ { key: title, node: value } ] : []}
37-
deleteLabel={() => onChange?.(undefined, '')}
38-
categoryName={title}
39-
showToolbarItem={showToolbarItem}
40-
>
41-
<SearchInput
42-
searchInputId={filterId}
43-
value={value}
44-
onChange={(e, inputValue) => onChange?.(e, trimValue ? inputValue.trim() : inputValue)}
45-
onClear={onClear}
46-
placeholder={`Filter by ${title}`}
47-
aria-label={`${title ?? filterId} filter`}
48-
data-ouia-component-id={`${ouiaId}-input`}
49-
{...props}
50-
/>
51-
</ToolbarFilter>
52-
);
32+
}: DataViewTextFilterProps) => {
33+
useEffect(() => {
34+
const handleKeyDown = (event: KeyboardEvent) => {
35+
// Only handle "/" key when not typing in an input, textarea, or contenteditable element
36+
if (event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey) {
37+
const target = event.target as HTMLElement;
38+
const isInputElement = target.tagName === 'INPUT' ||
39+
target.tagName === 'TEXTAREA' ||
40+
target.isContentEditable;
41+
42+
// Only focus if the filter is visible and we're not already in an input field
43+
if (showToolbarItem && !isInputElement) {
44+
// Find the input element by its ID (searchInputId prop)
45+
const inputElement = document.getElementById(filterId) as HTMLInputElement;
46+
if (inputElement) {
47+
event.preventDefault();
48+
inputElement.focus();
49+
}
50+
}
51+
}
52+
};
53+
54+
window.addEventListener('keydown', handleKeyDown);
55+
return () => {
56+
window.removeEventListener('keydown', handleKeyDown);
57+
};
58+
}, [showToolbarItem, filterId]);
59+
60+
return (
61+
<ToolbarFilter
62+
key={ouiaId}
63+
data-ouia-component-id={ouiaId}
64+
labels={value.length > 0 ? [ { key: title, node: value } ] : []}
65+
deleteLabel={() => onChange?.(undefined, '')}
66+
categoryName={title}
67+
showToolbarItem={showToolbarItem}
68+
>
69+
<SearchInput
70+
searchInputId={filterId}
71+
value={value}
72+
onChange={(e, inputValue) => onChange?.(e, trimValue ? inputValue.trim() : inputValue)}
73+
onClear={onClear}
74+
placeholder={`Filter by ${title}`}
75+
aria-label={`${title ?? filterId} filter`}
76+
data-ouia-component-id={`${ouiaId}-input`}
77+
{...props}
78+
/>
79+
</ToolbarFilter>
80+
);
81+
};
5382

5483
export default DataViewTextFilter;

0 commit comments

Comments
 (0)