Skip to content

Commit be2e5ab

Browse files
Home, End keys support (#430)
1 parent 826a73f commit be2e5ab

File tree

2 files changed

+44
-14
lines changed

2 files changed

+44
-14
lines changed

src/hooks/useAccessibility.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { MenuMode } from '../interface';
66
import { getMenuId } from '../context/IdContext';
77

88
// destruct to reduce minify size
9-
const { LEFT, RIGHT, UP, DOWN, ENTER, ESC } = KeyCode;
9+
const { LEFT, RIGHT, UP, DOWN, ENTER, ESC, HOME, END } = KeyCode;
1010

1111
const ArrowKeys = [UP, DOWN, LEFT, RIGHT];
1212

@@ -215,7 +215,7 @@ export default function useAccessibility<T extends HTMLElement>(
215215
return e => {
216216
const { which } = e;
217217

218-
if ([...ArrowKeys, ENTER, ESC].includes(which)) {
218+
if ([...ArrowKeys, ENTER, ESC, HOME, END].includes(which)) {
219219
// Convert key to elements
220220
let elements: Set<HTMLElement>;
221221
let key2element: Map<string, HTMLElement>;
@@ -259,12 +259,12 @@ export default function useAccessibility<T extends HTMLElement>(
259259
);
260260

261261
// Some mode do not have fully arrow operation like inline
262-
if (!offsetObj) {
262+
if (!offsetObj && which !== HOME && which !== END) {
263263
return;
264264
}
265265

266266
// Arrow prevent default to avoid page scroll
267-
if (ArrowKeys.includes(which)) {
267+
if (ArrowKeys.includes(which) || [HOME, END].includes(which)) {
268268
e.preventDefault();
269269
}
270270

@@ -295,7 +295,11 @@ export default function useAccessibility<T extends HTMLElement>(
295295
}
296296
};
297297

298-
if (offsetObj.sibling || !focusMenuElement) {
298+
if (
299+
[HOME, END].includes(which) ||
300+
offsetObj.sibling ||
301+
!focusMenuElement
302+
) {
299303
// ========================== Sibling ==========================
300304
// Find walkable focus menu element container
301305
let parentQueryContainer: HTMLElement;
@@ -306,13 +310,23 @@ export default function useAccessibility<T extends HTMLElement>(
306310
}
307311

308312
// Get next focus element
309-
const targetElement = getNextFocusElement(
313+
let targetElement;
314+
const focusableElements = getFocusableElements(
310315
parentQueryContainer,
311316
elements,
312-
focusMenuElement,
313-
offsetObj.offset,
314317
);
315-
318+
if (which === HOME) {
319+
targetElement = focusableElements[0];
320+
} else if (which === END) {
321+
targetElement = focusableElements[focusableElements.length - 1];
322+
} else {
323+
targetElement = getNextFocusElement(
324+
parentQueryContainer,
325+
elements,
326+
focusMenuElement,
327+
offsetObj.offset,
328+
);
329+
}
316330
// Focus menu item
317331
tryFocus(targetElement);
318332

tests/Keyboard.spec.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,35 +73,51 @@ describe('Menu.Keyboard', () => {
7373

7474
const wrapper = mount(<App />, { attachTo: holder });
7575

76-
wrapper.setState({ items: [0, 1] });
77-
await wrapper.flush();
78-
7976
// First item
8077
keyDown(wrapper, KeyCode.DOWN);
8178
expect(wrapper.isActive(0)).toBeTruthy();
8279

8380
// Next item
8481
keyDown(wrapper, KeyCode.DOWN);
8582
expect(wrapper.isActive(1)).toBeTruthy();
83+
84+
// Very first item
85+
keyDown(wrapper, KeyCode.HOME);
86+
expect(wrapper.isActive(0)).toBeTruthy();
87+
88+
// Very last item
89+
keyDown(wrapper, KeyCode.END);
90+
expect(wrapper.isActive(2)).toBeTruthy();
8691
});
8792

8893
it('Skip disabled item', () => {
8994
const wrapper = mount(
9095
<Menu defaultActiveFirst>
96+
<MenuItem disabled />
9197
<MenuItem key="1">1</MenuItem>
9298
<MenuItem disabled />
9399
<MenuItem key="2">2</MenuItem>
100+
<MenuItem disabled />
94101
</Menu>,
95102
{ attachTo: holder },
96103
);
97104

98105
// Next item
99106
keyDown(wrapper, KeyCode.DOWN);
100-
expect(wrapper.isActive(2)).toBeTruthy();
107+
keyDown(wrapper, KeyCode.DOWN);
108+
expect(wrapper.isActive(3)).toBeTruthy();
101109

102110
// Back to first item
103111
keyDown(wrapper, KeyCode.UP);
104-
expect(wrapper.isActive(0)).toBeTruthy();
112+
expect(wrapper.isActive(1)).toBeTruthy();
113+
114+
// To the last available item
115+
keyDown(wrapper, KeyCode.END);
116+
expect(wrapper.isActive(3)).toBeTruthy();
117+
118+
// To the first available item
119+
keyDown(wrapper, KeyCode.HOME);
120+
expect(wrapper.isActive(1)).toBeTruthy();
105121
});
106122

107123
it('Enter to open menu and active first item', () => {

0 commit comments

Comments
 (0)