Skip to content

Commit 8c08474

Browse files
Merge pull request #598 from catho/QTM-705
feat(QTM-705): Fix for focusing on other page elements when the "esc" key is pressed
2 parents 191b468 + 16a5238 commit 8c08474

File tree

3 files changed

+85
-21
lines changed

3 files changed

+85
-21
lines changed

components/DropdownLight/DropdownLight.jsx

+37-15
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,14 @@ const DropdownLight = ({
210210
const [cursor, setCursor] = useState(-1);
211211
const buttonRef = useRef();
212212
const listOptions = useRef();
213+
const ButtonIsClosedAndFocused = () =>
214+
!isOpen && document.activeElement === buttonRef.current;
213215

214-
const downPress = useKeyPress('ArrowDown');
215-
const upPress = useKeyPress('ArrowUp');
216+
const downPress = useKeyPress('ArrowDown', ButtonIsClosedAndFocused);
217+
const upPress = useKeyPress('ArrowUp', ButtonIsClosedAndFocused);
216218
const enterPress = useKeyPress('Enter');
219+
const escapePress = useKeyPress('Escape');
217220
const focusedItemIndex = useKeyboardSearchItems(items, cursor, isOpen);
218-
const EscapeKeyPressValue = 'Escape';
219221

220222
const handleToggleDropdown = () => {
221223
setIsOpen(!isOpen);
@@ -249,44 +251,64 @@ const DropdownLight = ({
249251
}
250252
};
251253

252-
const handleEscPress = ({ key }) => {
253-
if (key === EscapeKeyPressValue) {
254+
useEffect(() => {
255+
if (isOpen && escapePress) {
254256
setIsOpen(false);
255257
setCursor(-1);
256258
buttonRef.current.focus();
257259
}
258-
};
260+
}, [escapePress, isOpen]);
259261

260262
useEffect(() => {
263+
const GetNextCursor = (currentCursor) =>
264+
currentCursor < items.length - 1 ? currentCursor + 1 : currentCursor;
265+
261266
if (isOpen && downPress) {
262-
const selectedCursor = cursor < items.length - 1 ? cursor + 1 : cursor;
267+
const selectedCursor = GetNextCursor(cursor);
263268
setCursor(selectedCursor);
264269
}
270+
271+
if (downPress && ButtonIsClosedAndFocused()) {
272+
const selectedItemIndex = items.findIndex(
273+
(item) => (item?.label || item) === selectedOptionItem,
274+
);
275+
const selectedCursor = GetNextCursor(selectedItemIndex);
276+
277+
if (selectedItemIndex !== selectedCursor) {
278+
selectItem(items[selectedCursor]);
279+
}
280+
}
265281
}, [downPress, items, isOpen]);
266282

267283
useEffect(() => {
268284
if (isOpen && upPress && cursor > 0) {
269285
const selectedCursor = cursor - 1;
270286
setCursor(selectedCursor);
271287
}
288+
289+
if (upPress && ButtonIsClosedAndFocused()) {
290+
const selectedItemIndex = items.findIndex(
291+
(item) => (item?.label || item) === selectedOptionItem,
292+
);
293+
const previousCursor =
294+
selectedItemIndex > 0 ? selectedItemIndex - 1 : selectedItemIndex;
295+
const selectedCursor = previousCursor;
296+
297+
if (selectedItemIndex !== selectedCursor) {
298+
selectItem(items[selectedCursor]);
299+
}
300+
}
272301
}, [upPress, items, isOpen]);
273302

274303
useEffect(() => {
275304
window.addEventListener('click', handleClickOutside);
276-
window.addEventListener('keydown', handleEscPress);
277305
return () => {
278306
window.removeEventListener('click', handleClickOutside);
279-
window.removeEventListener('keydown', handleEscPress);
280307
};
281308
}, []);
282309

283310
useEffect(() => {
284-
if (
285-
enterPress &&
286-
!isOpen &&
287-
document.activeElement === buttonRef.current &&
288-
selectedOptionItem
289-
) {
311+
if (enterPress && ButtonIsClosedAndFocused() && selectedOptionItem) {
290312
const selectedItemIndex = items.findIndex(
291313
(item) => (item?.label || item) === selectedOptionItem,
292314
);

components/DropdownLight/DropdownLight.unit.test.jsx

+42-3
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,17 @@ describe('<DropdownLight />', () => {
171171

172172
it('should close Dropdown Options when user press Escape', async () => {
173173
render(<DropdownLight items={itemsStringMock} />);
174+
const dropdownButton = screen.getByRole('button', {
175+
name: 'abrir lista de itens',
176+
});
174177

178+
await userEvent.click(dropdownButton);
175179
await userEvent.keyboard(ESCAPE_KEY_CODE);
176180

177181
expect(screen.queryByRole('list')).not.toBeInTheDocument();
178182

179-
expect(
180-
screen.getByRole('button', { name: 'abrir lista de itens' }),
181-
).toBeInTheDocument();
183+
expect(dropdownButton).toBeInTheDocument();
184+
expect(dropdownButton).toHaveFocus();
182185
});
183186

184187
it('should allow User to select an option using only keyboard', async () => {
@@ -200,9 +203,13 @@ describe('<DropdownLight />', () => {
200203

201204
await userEvent.keyboard(ENTER_KEY_CODE);
202205
const input = screen.getByRole('textbox', { hidden: true });
206+
const dropdownButton = screen.getByRole('button', {
207+
name: 'abrir lista de itens',
208+
});
203209

204210
expect(input.value).toEqual(bananaItemMock);
205211
expect(screen.queryByRole('list')).not.toBeInTheDocument();
212+
expect(dropdownButton).toHaveFocus();
206213
});
207214

208215
it('should allow the User to navigate between items using the keys with the initial letter of each item', async () => {
@@ -280,4 +287,36 @@ describe('<DropdownLight />', () => {
280287

281288
expect(selectionItemImage).toBeInTheDocument();
282289
});
290+
291+
it('should allow to select an item from the list using the keyboard and without opening the list of options', async () => {
292+
const onChangeMock = jest.fn();
293+
render(<DropdownLight items={itemsObjectMock} onChange={onChangeMock} />);
294+
295+
await userEvent.tab();
296+
297+
const dropdownButton = screen.getByRole('button', {
298+
name: 'abrir lista de itens',
299+
});
300+
expect(dropdownButton).toHaveFocus();
301+
302+
await userEvent.keyboard(ARROW_DOWN_KEY_CODE);
303+
304+
expect(onChangeMock).toHaveBeenCalledWith({
305+
label: 'Lemon',
306+
value: 'Lemon',
307+
});
308+
309+
expect(screen.queryByRole('list')).not.toBeInTheDocument();
310+
expect(dropdownButton).toHaveFocus();
311+
312+
await userEvent.keyboard(ARROW_DOWN_KEY_CODE);
313+
314+
expect(onChangeMock).toHaveBeenCalledWith({
315+
label: 'Lime',
316+
value: 'Lime',
317+
});
318+
319+
expect(screen.queryByRole('list')).not.toBeInTheDocument();
320+
expect(dropdownButton).toHaveFocus();
321+
});
283322
});

components/DropdownLight/SubComponents/UseKeyPress.jsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { useState, useEffect } from 'react';
22

3-
const useKeyPress = (targetKey) => {
3+
const useKeyPress = (targetKey, isPreventDefault = () => false) => {
44
const [keyPressed, setKeyPressed] = useState(false);
55

66
/* istanbul ignore next */
7-
const downHandler = ({ key }) => {
8-
if (key === targetKey) {
7+
const downHandler = (event) => {
8+
if (event.key === targetKey) {
9+
if (isPreventDefault()) {
10+
event.preventDefault();
11+
}
912
setKeyPressed(true);
1013
}
1114
};

0 commit comments

Comments
 (0)