diff --git a/src/Utils.ts b/src/Utils.ts
index 4d8e1183..ee02b827 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -557,10 +557,8 @@ export function isDisplayNone(element: HTMLElement): boolean {
return true;
}
- if (
- element.parentElement?.offsetParent === null &&
- elementDocument.body !== element.parentElement
- ) {
+ // A visible fixed or display:contents parent can also have no offsetParent.
+ if (element.getClientRects().length === 0) {
return true;
}
}
diff --git a/tests/ObservedElement.test.tsx b/tests/ObservedElement.test.tsx
index 1a766dff..a2606181 100644
--- a/tests/ObservedElement.test.tsx
+++ b/tests/ObservedElement.test.tsx
@@ -17,6 +17,116 @@ describe("Focusable", () => {
await BroTest.bootstrapTabsterPage({ observed: true });
});
+ it.each([
+ ["button", "block"],
+ ["input", "block"],
+ ["div", "block"],
+ ["button", "contents"],
+ ])(
+ "should focus a visible fixed %s inside a fixed parent with display %s",
+ async (tag, display) => {
+ await new BroTest.BroTest(
+
+
+
+ {React.createElement(
+ tag,
+ {
+ id: "target",
+ tabIndex: -1,
+ style: { position: "fixed" },
+ ...getTabsterAttribute({
+ observed: { names: ["target"] },
+ }),
+ },
+ tag === "input" ? undefined : "Target"
+ )}
+
+
+ )
+ .eval(async () => {
+ const { observedElement, dom } = getTabsterTestVariables();
+ const start = dom?.getElementById(document, "start");
+ const target = dom?.getElementById(document, "target");
+ if (!observedElement || !start || !target) {
+ throw new Error("Missing observed test setup");
+ }
+
+ let nativeFocused = false;
+ target.addEventListener(
+ "focus",
+ () => {
+ nativeFocused = true;
+ },
+ { once: true }
+ );
+ target.focus();
+ start.focus();
+
+ const request = observedElement.requestFocus("target", 500);
+ return {
+ nativeFocused,
+ result: await request.result,
+ status: request.status,
+ };
+ })
+ .check(({ nativeFocused, result, status }) => {
+ expect(nativeFocused).toBe(true);
+ expect(result).toBe(true);
+ expect(status).toBe(
+ ObservedElementRequestStatuses.Succeeded
+ );
+ })
+ .activeElement((el) => {
+ expect(el?.attributes.id).toBe("target");
+ });
+ }
+ );
+
+ it("should wait for a hidden fixed parent to become visible", async () => {
+ await new BroTest.BroTest(
+
+ )
+ .eval(async () => {
+ const { observedElement, dom } = getTabsterTestVariables();
+ const parent = dom?.getElementById(document, "parent");
+ if (!observedElement || !parent) {
+ throw new Error("Missing observed test setup");
+ }
+
+ const request = observedElement.requestFocus("target", 1000);
+ const initialStatus = request.status;
+ parent.style.display = "block";
+ return {
+ initialStatus,
+ result: await request.result,
+ status: request.status,
+ };
+ })
+ .check(({ initialStatus, result, status }) => {
+ expect(initialStatus).toBe(
+ ObservedElementRequestStatuses.Waiting
+ );
+ expect(result).toBe(true);
+ expect(status).toBe(ObservedElementRequestStatuses.Succeeded);
+ })
+ .activeElement((el) => {
+ expect(el?.attributes.id).toBe("target");
+ });
+ });
+
it("should forward focus options", async () => {
const name = "test";
const focusOptions: Types.TabsterFocusOptions = {