From f34aa4052a90074110d18df2f7ba6d4a7d52b953 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:36:10 +0000 Subject: [PATCH 1/3] fix: restore keyboard focus to location dropdown after closing via Escape key When the location dropdown was closed via the Escape key, keyboard focus was lost, making it difficult for keyboard-only users to continue navigating. This fix ensures focus returns to the dropdown trigger element when the popup closes, improving accessibility for keyboard users. Fixes #641 Co-authored-by: KethanaReddy7 --- .../clipperUI/components/sectionPicker.tsx | 7 ++++ .../components/sectionPicker_tests.tsx | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/scripts/clipperUI/components/sectionPicker.tsx b/src/scripts/clipperUI/components/sectionPicker.tsx index 43339d23..9bb2e265 100644 --- a/src/scripts/clipperUI/components/sectionPicker.tsx +++ b/src/scripts/clipperUI/components/sectionPicker.tsx @@ -54,6 +54,13 @@ export class SectionPickerClass extends ComponentBase { + let clipperState = MockProps.getMockClipperState(); + let mockNotebooks = MockProps.getMockNotebooks(); + initializeClipperStorage(JSON.stringify(mockNotebooks), undefined); + + let popupToggleCalled = false; + let component = { popupToggleCalled = true; }} + clipperState={clipperState} />; + let controllerInstance = MithrilUtils.mountToFixture(component); + + // Open the popup first + MithrilUtils.simulateAction(() => { + document.getElementById(TestConstants.Ids.sectionLocationContainer).click(); + }); + + // Focus another element to simulate focus being elsewhere + let anotherElement = document.createElement("button"); + document.body.appendChild(anotherElement); + anotherElement.focus(); + + // Close the popup by calling onPopupToggle with false + controllerInstance.onPopupToggle(false); + + // Verify focus was restored to the location container + strictEqual(document.activeElement.id, TestConstants.Ids.sectionLocationContainer, + "Focus should be restored to sectionLocationContainer when popup closes"); + ok(popupToggleCalled, "The parent onPopupToggle callback should have been called"); + + // Clean up + document.body.removeChild(anotherElement); + }); } } From 0f016edb6892f5ea3b9394d4d1602b2128331000 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 04:30:50 +0000 Subject: [PATCH 2/3] fix: defer focus restoration to allow DOM to stabilize after popup close The focus was being lost because it was set immediately during the onPopupToggle callback, but the OneNotePicker component's internal DOM manipulation was happening after the focus call. By using setTimeout(0), the focus is now deferred until after the event loop allows the DOM to stabilize. Co-authored-by: KethanaReddy7 --- .../clipperUI/components/sectionPicker.tsx | 11 +++++++---- .../components/sectionPicker_tests.tsx | 19 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/scripts/clipperUI/components/sectionPicker.tsx b/src/scripts/clipperUI/components/sectionPicker.tsx index 9bb2e265..7c89963c 100644 --- a/src/scripts/clipperUI/components/sectionPicker.tsx +++ b/src/scripts/clipperUI/components/sectionPicker.tsx @@ -57,10 +57,13 @@ export class SectionPickerClass extends ComponentBase { + const locationContainer = document.getElementById(Constants.Ids.sectionLocationContainer); + if (locationContainer) { + locationContainer.focus(); + } + }, 0); } this.props.onPopupToggle(shouldNowBeOpen); } diff --git a/src/tests/clipperUI/components/sectionPicker_tests.tsx b/src/tests/clipperUI/components/sectionPicker_tests.tsx index c5c20e39..418925a1 100644 --- a/src/tests/clipperUI/components/sectionPicker_tests.tsx +++ b/src/tests/clipperUI/components/sectionPicker_tests.tsx @@ -273,7 +273,8 @@ export class SectionPickerTests extends TestModule { strictEqual(actual, undefined, "The section info should be formatted correctly"); }); - test("onPopupToggle should restore focus to sectionLocationContainer when popup closes", () => { + test("onPopupToggle should restore focus to sectionLocationContainer when popup closes", (assert: QUnitAssert) => { + let done = assert.async(); let clipperState = MockProps.getMockClipperState(); let mockNotebooks = MockProps.getMockNotebooks(); initializeClipperStorage(JSON.stringify(mockNotebooks), undefined); @@ -297,13 +298,17 @@ export class SectionPickerTests extends TestModule { // Close the popup by calling onPopupToggle with false controllerInstance.onPopupToggle(false); - // Verify focus was restored to the location container - strictEqual(document.activeElement.id, TestConstants.Ids.sectionLocationContainer, - "Focus should be restored to sectionLocationContainer when popup closes"); - ok(popupToggleCalled, "The parent onPopupToggle callback should have been called"); + // Wait for the deferred focus (setTimeout) to execute + setTimeout(() => { + // Verify focus was restored to the location container + strictEqual(document.activeElement.id, TestConstants.Ids.sectionLocationContainer, + "Focus should be restored to sectionLocationContainer when popup closes"); + ok(popupToggleCalled, "The parent onPopupToggle callback should have been called"); - // Clean up - document.body.removeChild(anotherElement); + // Clean up + document.body.removeChild(anotherElement); + done(); + }, 10); }); } } From eb4a7c7637d78ebc3fb24527bb7f7ad10d6e3014 Mon Sep 17 00:00:00 2001 From: KethanaReddy7 Date: Wed, 18 Mar 2026 12:33:01 +0530 Subject: [PATCH 3/3] Add escape handler --- .../clipperUI/components/sectionPicker.tsx | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/scripts/clipperUI/components/sectionPicker.tsx b/src/scripts/clipperUI/components/sectionPicker.tsx index 7c89963c..204929ce 100644 --- a/src/scripts/clipperUI/components/sectionPicker.tsx +++ b/src/scripts/clipperUI/components/sectionPicker.tsx @@ -54,16 +54,6 @@ export class SectionPickerClass extends ComponentBase { - const locationContainer = document.getElementById(Constants.Ids.sectionLocationContainer); - if (locationContainer) { - locationContainer.focus(); - } - }, 0); } this.props.onPopupToggle(shouldNowBeOpen); } @@ -245,6 +235,32 @@ export class SectionPickerClass extends ComponentBase { + if (ev.keyCode === escKeyCode) { + // Check if the dropdown popup is currently visible + let sectionPickerPopup = document.querySelector(".SectionPickerPopup"); + if (sectionPickerPopup) { + // The popup is open - schedule focus return after it closes + setTimeout(() => { + let locationButton = document.getElementById(Constants.Ids.sectionLocationContainer); + if (locationButton) { + locationButton.focus(); + } + }, 10); + } + } + }; + // Use capture phase to run before OneNotePicker's handler + document.addEventListener("keydown", handleKeyDown, true); + } + } + addSrOnlyLocationDiv(element: HTMLElement) { const pickerLinkElement = document.getElementById(Constants.Ids.sectionLocationContainer); if (!pickerLinkElement) { @@ -256,6 +272,9 @@ export class SectionPickerClass extends ComponentBase