Skip to content

Commit 0836c9f

Browse files
Cathy0123456789Abi107717
authored andcommitted
Usability: Fix keyboard navigation in application carousel (#2027)
1 parent a2ce5fd commit 0836c9f

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

src/main/webapp/app/shared/components/organisms/application-carousel/application-carousel.component.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ export class ApplicationCarouselComponent {
7777

7878
@HostListener('document:keydown', ['$event'])
7979
handleGlobalKeyDown(event: KeyboardEvent): void {
80+
// 1. Standard Guard Clauses
81+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) {
82+
return;
83+
}
84+
85+
// 2. Focus Guard
86+
const active = document.activeElement as HTMLElement | null;
87+
if (active) {
88+
const isEditable = ['INPUT', 'TEXTAREA'].includes(active.tagName) || active.isContentEditable;
89+
const isInsideEditable = !!active.closest('[contenteditable="true"]');
90+
91+
if (isEditable || isInsideEditable) {
92+
return;
93+
}
94+
}
95+
96+
// 3. Navigation Logic
8097
switch (event.key) {
8198
case 'ArrowRight':
8299
event.preventDefault();

src/test/webapp/app/shared/components/organisms/application-carousel/application-carousel.component.spec.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { provideFontAwesomeTesting } from 'util/fontawesome.testing';
77

88
import { ApplicationCarouselComponent } from 'app/shared/components/organisms/application-carousel/application-carousel.component';
99
import { ApplicationEvaluationDetailDTO } from 'app/generated/model/applicationEvaluationDetailDTO';
10-
import { BREAKPOINT_QUERIES } from 'app/shared/constants/breakpoints';
1110
import { ApplicationDetailDTO } from 'app/generated/model/applicationDetailDTO';
1211
import { ProfessorDTO } from 'app/generated/model/professorDTO';
1312

@@ -222,5 +221,64 @@ describe('ApplicationCarouselComponent', () => {
222221
expect(spyNext).not.toHaveBeenCalled();
223222
expect(spyPrev).not.toHaveBeenCalled();
224223
});
224+
225+
it('should not navigate when focus is inside a textarea', () => {
226+
const textarea = document.createElement('textarea');
227+
document.body.appendChild(textarea);
228+
textarea.focus();
229+
230+
const spyNext = vi.spyOn(component, 'loadNext');
231+
component.handleGlobalKeyDown(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
232+
233+
expect(spyNext).not.toHaveBeenCalled();
234+
235+
document.body.removeChild(textarea);
236+
});
237+
238+
it('should not navigate when focus is inside an input', () => {
239+
const input = document.createElement('input');
240+
document.body.appendChild(input);
241+
input.focus();
242+
243+
const spyPrev = vi.spyOn(component, 'loadPrev');
244+
component.handleGlobalKeyDown(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
245+
246+
expect(spyPrev).not.toHaveBeenCalled();
247+
248+
document.body.removeChild(input);
249+
});
250+
251+
it('should not navigate when an element with contenteditable is focused', () => {
252+
const div = document.createElement('div');
253+
div.setAttribute('contenteditable', 'true');
254+
document.body.appendChild(div);
255+
div.focus();
256+
257+
const spyNext = vi.spyOn(component, 'loadNext');
258+
component.handleGlobalKeyDown(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
259+
260+
expect(spyNext).not.toHaveBeenCalled();
261+
262+
document.body.removeChild(div);
263+
});
264+
265+
it('should not navigate when modifier keys are pressed', () => {
266+
const spyNext = vi.spyOn(component, 'loadNext');
267+
component.handleGlobalKeyDown(new KeyboardEvent('keydown', { key: 'ArrowRight', ctrlKey: true }));
268+
component.handleGlobalKeyDown(new KeyboardEvent('keydown', { key: 'ArrowRight', metaKey: true }));
269+
component.handleGlobalKeyDown(new KeyboardEvent('keydown', { key: 'ArrowRight', altKey: true }));
270+
271+
expect(spyNext).not.toHaveBeenCalled();
272+
});
273+
274+
it('should not navigate when event already prevented', () => {
275+
const spyNext = vi.spyOn(component, 'loadNext');
276+
const ev = new KeyboardEvent('keydown', { key: 'ArrowRight' });
277+
Object.defineProperty(ev, 'defaultPrevented', { get: () => true });
278+
279+
component.handleGlobalKeyDown(ev);
280+
281+
expect(spyNext).not.toHaveBeenCalled();
282+
});
225283
});
226284
});

0 commit comments

Comments
 (0)