Skip to content

Commit a9a0902

Browse files
committed
fix: Scroll active element into view while focusing
It looks like using element.focus() doesn't always scroll the element into view if a parent element has scrolling enabled. This caused a problem with large dropdown menus and using the ArrowUp key to trigger the menu that would not have the current element visible until the ArrowUp key was pressed again.
1 parent 3f2f4e8 commit a9a0902

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

packages/utils/src/wia-aria/__tests__/focusElementWithin.ts

+59
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { mocked } from "ts-jest/utils";
22
import focusElementWithin from "../focusElementWithin";
33
import getFocusableElements_ from "../getFocusableElements";
4+
import scrollIntoView_ from "../../scrollIntoView";
45

56
jest.mock("../getFocusableElements");
7+
jest.mock("../../scrollIntoView");
68

79
const getFocusableElements = mocked(getFocusableElements_);
10+
const scrollIntoView = mocked(scrollIntoView_);
811
const ELEMENT = document.createElement("div");
912
const element1 = document.createElement("button");
1013
element1.className = "element element--1";
@@ -26,6 +29,7 @@ describe("focusElementWithin", () => {
2629
focus2.mockClear();
2730
focus3.mockClear();
2831
getFocusableElements.mockClear();
32+
scrollIntoView.mockClear();
2933
});
3034

3135
it("should throw an error if it can not find a focusable element", () => {
@@ -132,4 +136,59 @@ describe("focusElementWithin", () => {
132136
expect(focus2).not.toBeCalled();
133137
expect(focus3).toBeCalled();
134138
});
139+
140+
it("should call scrollIntoView if preventScroll is false and the container element is not the document", () => {
141+
elements.forEach((element) => {
142+
document.body.appendChild(element);
143+
});
144+
focusElementWithin(document, "first", false, false, elements);
145+
focusElementWithin(document, "last", false, false, elements);
146+
focusElementWithin(document, ".element", false, false, elements);
147+
focusElementWithin(document, "first", true, false, elements);
148+
focusElementWithin(document, "last", true, false, elements);
149+
focusElementWithin(document, ".element", true, false, elements);
150+
focusElementWithin(document, "first", false, true, elements);
151+
focusElementWithin(document, "last", false, true, elements);
152+
focusElementWithin(document, ".element", false, true, elements);
153+
focusElementWithin(document, "first", true, true, elements);
154+
focusElementWithin(document, "last", true, true, elements);
155+
focusElementWithin(document, ".element", true, true, elements);
156+
elements.forEach((element) => {
157+
document.body.removeChild(element);
158+
});
159+
160+
expect(scrollIntoView).not.toBeCalled();
161+
162+
const container = document.createElement("div");
163+
elements.forEach((element) => {
164+
container.appendChild(element);
165+
});
166+
167+
focusElementWithin(container, "first", false, false, elements);
168+
expect(scrollIntoView).toBeCalledWith(container, elements[0]);
169+
170+
focusElementWithin(container, "last", false, false, elements);
171+
expect(scrollIntoView).toBeCalledWith(container, elements[2]);
172+
173+
focusElementWithin(
174+
container,
175+
".element:nth-child(2)",
176+
false,
177+
false,
178+
elements
179+
);
180+
expect(scrollIntoView).toBeCalledWith(container, elements[1]);
181+
182+
scrollIntoView.mockClear();
183+
focusElementWithin(container, "first", false, true, elements);
184+
focusElementWithin(container, "last", false, true, elements);
185+
focusElementWithin(
186+
container,
187+
".element:nth-child(2)",
188+
false,
189+
true,
190+
elements
191+
);
192+
expect(scrollIntoView).not.toBeCalled();
193+
});
135194
});

packages/utils/src/wia-aria/focusElementWithin.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import getFocusableElements from "./getFocusableElements";
2+
import scrollIntoView from "../scrollIntoView";
23

34
export type Focus = "first" | "last" | string;
45

@@ -45,4 +46,7 @@ export default function focusElementWithin(
4546
}
4647

4748
el.focus({ preventScroll });
49+
if (!preventScroll && container !== document) {
50+
scrollIntoView(container as HTMLElement, el);
51+
}
4852
}

0 commit comments

Comments
 (0)