diff --git a/packages/csr/src/reactive-runtime.ts b/packages/csr/src/reactive-runtime.ts index ad2738b0..0d232256 100644 --- a/packages/csr/src/reactive-runtime.ts +++ b/packages/csr/src/reactive-runtime.ts @@ -2927,11 +2927,18 @@ export const REACTIVE_RUNTIME = String.raw` var dialogRestoreFocus = null; var dialogScrollLock = null; + /* + * Open/closed is expressed by the data-show marker these components already + * emit, not by measured size. Measuring looks more thorough but made the + * trap and the scroll lock impossible to cover: the test DOM reports every + * element as zero-sized, so a dialog never counted as open and none of this + * behaviour ran under test. + */ function isDialogVisible(dialog) { - if (!dialog || !dialog.getBoundingClientRect) return false; + if (!dialog || !dialog.isConnected) return false; + if (dialog.hasAttribute("hidden")) return false; if (dialog.closest('[data-show="false"]')) return false; - var rect = dialog.getBoundingClientRect(); - return rect.width > 0 && rect.height > 0; + return true; } function focusableWithin(dialog) { @@ -2939,8 +2946,10 @@ export const REACTIVE_RUNTIME = String.raw` var candidates = dialog.querySelectorAll(FOCUSABLE_SELECTOR); for (var index = 0; index < candidates.length; index += 1) { var candidate = candidates[index]; - var rect = candidate.getBoundingClientRect(); - if (rect.width > 0 || rect.height > 0) found.push(candidate); + // Same rule as the roving items: markers, not measurement. + if (candidate.hasAttribute("hidden")) continue; + if (candidate.closest('[data-show="false"]')) continue; + found.push(candidate); } return found; } diff --git a/packages/csr/test/reactive.test.ts b/packages/csr/test/reactive.test.ts index bef6017c..b71c47e2 100644 --- a/packages/csr/test/reactive.test.ts +++ b/packages/csr/test/reactive.test.ts @@ -993,3 +993,40 @@ test("nested roving groups do not capture the outer group items", () => { a.dispatchEvent(new win.KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true })); expect(doc.activeElement!.id).toBe("b"); }); + +test("opening a modal dialog traps Tab inside it", () => { + const win = mount( + `