Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
0371c1f
fix: add keyboard focus to success panel and region selecting panel
github-actions[bot] Feb 25, 2026
9cbd85a
fix(a11y): Update extension button accessible name for screen readers
github-actions[bot] Feb 25, 2026
26e2c24
fix: Add explicit tabIndex to mode buttons for proper focus order
github-actions[bot] Feb 26, 2026
b7e519e
fix(a11y): Update extension button name to "OneNote Web Clipper"
github-actions[bot] Feb 27, 2026
cd36991
Merge pull request #632 from OneNoteDev/claude/issue-631-20260226-0922
KethanaReddy7 Feb 27, 2026
4b19215
Merge pull request #630 from OneNoteDev/claude/issue-629-20260225-1056
KethanaReddy7 Mar 2, 2026
14d4761
fix(a11y): hide Location label from Voice Access to prevent number ov…
github-actions[bot] Mar 9, 2026
ecc89bb
Fix: Implement focus trap for Clip Successful popup (A11y)
github-actions[bot] Mar 9, 2026
6652b2a
Revert focus changes from regionSelectingPanel.tsx
github-actions[bot] Mar 10, 2026
5a2a5d7
Revert "Revert focus changes from regionSelectingPanel.tsx"
github-actions[bot] Mar 10, 2026
81c3004
Reapply "Revert focus changes from regionSelectingPanel.tsx"
github-actions[bot] Mar 10, 2026
36dbfd5
Refine focus trapping for specific panels
KethanaReddy7 Mar 10, 2026
a74ada8
Refactor focus trap tests for UI panels
KethanaReddy7 Mar 10, 2026
d1df05f
Revert "Reapply "Revert focus changes from regionSelectingPanel.tsx""
github-actions[bot] Mar 10, 2026
4d89853
Merge pull request #637 from OneNoteDev/claude/issue-636-20260309-0548
KethanaReddy7 Mar 11, 2026
1b0f597
Merge pull request #635 from OneNoteDev/claude/issue-628-20260225-0907
KethanaReddy7 Mar 11, 2026
dd82ed0
Merge pull request #638 from OneNoteDev/claude/issue-634-20260309-0501
KethanaReddy7 Mar 11, 2026
2458673
fix(a11y): Focus on selected item when location dropdown opens via ke…
github-actions[bot] Mar 12, 2026
f34aa40
fix: restore keyboard focus to location dropdown after closing via Es…
github-actions[bot] Mar 12, 2026
de64190
fix(a11y): Use setTimeout instead of requestAnimationFrame for popup …
github-actions[bot] Mar 18, 2026
0f016ed
fix: defer focus restoration to allow DOM to stabilize after popup close
github-actions[bot] Mar 18, 2026
6918ceb
Add focus to location dropdown
KethanaReddy7 Mar 18, 2026
e0525de
update test
KethanaReddy7 Mar 18, 2026
eb4a7c7
Add escape handler
KethanaReddy7 Mar 18, 2026
d37e006
Initial plan
Copilot Mar 23, 2026
b97c0bb
Fix high contrast selected state visibility for font radio buttons an…
Copilot Mar 23, 2026
dfeb274
Remove mode button high contrast styling per review feedback
github-actions[bot] Mar 26, 2026
d5d8b2f
Merge pull request #650 from OneNoteDev/claude/issue-641-20260312-1033
KethanaReddy7 Mar 26, 2026
0004236
Merge pull request #648 from OneNoteDev/claude/issue-640-20260312-1031
KethanaReddy7 Mar 26, 2026
d2b3aaa
Merge pull request #649 from OneNoteDev/copilot/on-clipper-fix-high-c…
KethanaReddy7 Mar 26, 2026
44a6ec8
Initial plan
Copilot Mar 30, 2026
d54567b
Fix Voice Access showing numbers to background controls by adding rol…
Copilot Mar 30, 2026
9fb6f3e
Fix Voice Access background controls: set aria-hidden on document.bod…
Copilot Mar 30, 2026
271fb4c
Add inert alongside aria-hidden on body to fully suppress Voice Acces…
Copilot Mar 31, 2026
b57856e
Initial plan
Copilot Mar 31, 2026
5431c04
Fix keyboard focus for location dropdown when opened via keyboard
Copilot Mar 31, 2026
20594c7
Add Up/Down arrow key navigation in location dropdown popup
Copilot Apr 1, 2026
155b3d7
Fix arrow key navigation to skip hidden items inside collapsed notebooks
Copilot Apr 1, 2026
dd6f09e
Navigate using up and down arrow keys
KethanaReddy7 Apr 1, 2026
ca7891a
Merge branch 'user/kpeddireddy/LocationButtonTab' into copilot/fix-lo…
KethanaReddy7 Apr 1, 2026
fb7808e
Merge pull request #653 from OneNoteDev/copilot/fix-location-dropdown…
aanchalbhansali Apr 1, 2026
eab6121
Merge pull request #652 from OneNoteDev/copilot/verify-accessibility-…
KethanaReddy7 Apr 2, 2026
6796c44
Fix Build errors
KethanaReddy7 Apr 2, 2026
ed38733
Simplify code comments for readability
KethanaReddy7 Apr 2, 2026
e6cf958
Remove additional changes
KethanaReddy7 Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ gulp.task("tslint", function() {
var tsFiles = [
PATHS.SRC.ROOT + "**/*.ts",
PATHS.SRC.ROOT + "**/*.tsx",
"!" + PATHS.SRC.ROOT + "**/*.d.ts"
"!" + PATHS.SRC.ROOT + "**/*.d.ts",
"!" + PATHS.SRC.ROOT + "scripts/definitions/custom/aria-web-telemetry-*.d_internal.ts"
];

return gulp.src(tsFiles)
Expand Down
45 changes: 27 additions & 18 deletions src/scripts/clipperUI/components/modeButtonSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
});
};

private getPdfButtonProps(currentMode: ClipMode): PropsForModeElementNoAriaGrouping {
private getPdfButtonProps(currentMode: ClipMode, tabIndex: number): PropsForModeElementNoAriaGrouping {
if (this.props.clipperState.pageInfo.contentType !== OneNoteApi.ContentType.EnhancedUrl) {
return undefined;
}
Expand All @@ -27,11 +27,12 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
myMode: ClipMode.Pdf,
selected: currentMode === ClipMode.Pdf,
onModeSelected: this.onModeSelected.bind(this),
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.Pdf.Button.Tooltip")
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.Pdf.Button.Tooltip"),
tabIndex: tabIndex
};
}

private getAugmentationButtonProps(currentMode: ClipMode): PropsForModeElementNoAriaGrouping {
private getAugmentationButtonProps(currentMode: ClipMode, tabIndex: number): PropsForModeElementNoAriaGrouping {
if (this.props.clipperState.pageInfo.contentType === OneNoteApi.ContentType.EnhancedUrl) {
return undefined;
}
Expand All @@ -46,11 +47,12 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
myMode: ClipMode.Augmentation,
selected: buttonSelected,
onModeSelected: this.onModeSelected.bind(this),
tooltipText: augmentationTooltip
tooltipText: augmentationTooltip,
tabIndex: tabIndex
};
}

private getFullPageButtonProps(currentMode: ClipMode): PropsForModeElementNoAriaGrouping {
private getFullPageButtonProps(currentMode: ClipMode, tabIndex: number): PropsForModeElementNoAriaGrouping {
if (this.props.clipperState.pageInfo.contentType === OneNoteApi.ContentType.EnhancedUrl) {
return undefined;
}
Expand All @@ -61,11 +63,12 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
myMode: ClipMode.FullPage,
selected: currentMode === ClipMode.FullPage,
onModeSelected: this.onModeSelected.bind(this),
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.ScreenShot.Button.Tooltip")
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.ScreenShot.Button.Tooltip"),
tabIndex: tabIndex
};
}

private getRegionButtonProps(currentMode: ClipMode): PropsForModeElementNoAriaGrouping {
private getRegionButtonProps(currentMode: ClipMode, tabIndex: number): PropsForModeElementNoAriaGrouping {
let enableRegionClipping = this.props.clipperState.injectOptions && this.props.clipperState.injectOptions.enableRegionClipping;
let contextImageModeUsed = this.props.clipperState.invokeOptions && this.props.clipperState.invokeOptions.invokeMode === InvokeMode.ContextImage;

Expand All @@ -79,15 +82,16 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
myMode: ClipMode.Region,
selected: currentMode === ClipMode.Region,
onModeSelected: this.onModeSelected.bind(this),
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.MultipleRegions.Button.Tooltip")
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.MultipleRegions.Button.Tooltip"),
tabIndex: tabIndex
};
}

private getRegionButtonLabel(): string {
return "WebClipper.ClipType.Region.Button";
}

private getSelectionButtonProps(currentMode: ClipMode): PropsForModeElementNoAriaGrouping {
private getSelectionButtonProps(currentMode: ClipMode, tabIndex: number): PropsForModeElementNoAriaGrouping {
if (this.props.clipperState.invokeOptions.invokeMode !== InvokeMode.ContextTextSelection) {
return undefined;
}
Expand All @@ -98,11 +102,12 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
myMode: ClipMode.Selection,
selected: currentMode === ClipMode.Selection,
onModeSelected: this.onModeSelected.bind(this),
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.Selection.Button.Tooltip")
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.Selection.Button.Tooltip"),
tabIndex: tabIndex
};
}

private getBookmarkButtonProps(currentMode: ClipMode): PropsForModeElementNoAriaGrouping {
private getBookmarkButtonProps(currentMode: ClipMode, tabIndex: number): PropsForModeElementNoAriaGrouping {
if (this.props.clipperState.pageInfo.rawUrl.indexOf("file:///") === 0) {
return undefined;
}
Expand All @@ -113,20 +118,24 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
myMode: ClipMode.Bookmark,
selected: currentMode === ClipMode.Bookmark,
onModeSelected: this.onModeSelected.bind(this),
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.Bookmark.Button.Tooltip")
tooltipText: Localization.getLocalizedString("WebClipper.ClipType.Bookmark.Button.Tooltip"),
tabIndex: tabIndex
};
}

private getListOfButtons(): HTMLElement[] {
let currentMode = this.props.clipperState.currentMode.get();

// Base tabIndex for mode buttons - they should come before PDF options (60+) and location dropdown (70)
let baseTabIndex = 40;

let buttonProps = [
this.getFullPageButtonProps(currentMode),
this.getRegionButtonProps(currentMode),
this.getAugmentationButtonProps(currentMode),
this.getSelectionButtonProps(currentMode),
this.getBookmarkButtonProps(currentMode),
this.getPdfButtonProps(currentMode),
this.getFullPageButtonProps(currentMode, baseTabIndex),
this.getRegionButtonProps(currentMode, baseTabIndex + 1),
this.getAugmentationButtonProps(currentMode, baseTabIndex + 2),
this.getSelectionButtonProps(currentMode, baseTabIndex + 3),
this.getBookmarkButtonProps(currentMode, baseTabIndex + 4),
this.getPdfButtonProps(currentMode, baseTabIndex + 5),
];

let visibleButtons = [];
Expand Down
82 changes: 78 additions & 4 deletions src/scripts/clipperUI/components/sectionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,53 @@ export class SectionPickerClass extends ComponentBase<SectionPickerState, Sectio
Clipper.logger.logClickEvent(Log.Click.Label.sectionPickerLocationContainer);
}
this.props.onPopupToggle(shouldNowBeOpen);
if (shouldNowBeOpen) {
// Focus the selected section and attach arrow key navigation for keyboard users
setTimeout(() => {
let sectionPickerPopup = document.getElementById("sectionPickerContainer");

let curSectionId = this.state.curSection && this.state.curSection.section ? this.state.curSection.section.id : undefined;
let elementToFocus: HTMLElement;
if (curSectionId) {
elementToFocus = document.getElementById(curSectionId) as HTMLElement;
}
if (!elementToFocus && sectionPickerPopup) {
// Fall back to the first keyboard-navigable item in the section picker popup
elementToFocus = sectionPickerPopup.querySelector("[tabindex]:not([tabindex=\"-1\"])") as HTMLElement;
}
if (elementToFocus) {
elementToFocus.focus();
}

// Attach Up/Down arrow key navigation (OneNotePicker only handles Enter/Tab)
if (sectionPickerPopup && !sectionPickerPopup.getAttribute("data-arrow-key-handler-attached")) {
sectionPickerPopup.setAttribute("data-arrow-key-handler-attached", "true");
sectionPickerPopup.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.which !== Constants.KeyCodes.up && e.which !== Constants.KeyCodes.down) {
return;
}
e.preventDefault();
let focusableItems = Array.prototype.slice.call(
sectionPickerPopup.querySelectorAll("[tabindex]:not([tabindex=\"-1\"])")
).filter((el) => {
let parent = (el as HTMLElement).parentElement;
return !parent || !parent.closest(".Closed");
}) as HTMLElement[];
if (focusableItems.length === 0) {
return;
}
let currentIndex = focusableItems.indexOf(document.activeElement as HTMLElement);
if (e.which === Constants.KeyCodes.up) {
let prevIndex = currentIndex <= 0 ? 0 : currentIndex - 1;
focusableItems[prevIndex].focus();
} else {
let nextIndex = currentIndex >= focusableItems.length - 1 ? focusableItems.length - 1 : currentIndex + 1;
focusableItems[nextIndex].focus();
}
}, true);
}
}, 0);
}
}

// Returns true if successful; false otherwise
Expand Down Expand Up @@ -235,6 +282,30 @@ export class SectionPickerClass extends ComponentBase<SectionPickerState, Sectio
};
}

// Attach escape key handler to return focus to the dropdown button when Escape is pressed
attachEscapeFocusHandler(element: HTMLElement, isInitialized: boolean) {
if (!isInitialized) {
const escKeyCode = 27;
const handleKeyDown = (ev: KeyboardEvent) => {
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) {
Expand All @@ -246,6 +317,9 @@ export class SectionPickerClass extends ComponentBase<SectionPickerState, Sectio
srDiv.setAttribute("class", Constants.Classes.srOnly);
// Make srDiv the first child of pickerLinkElement
pickerLinkElement.insertBefore(srDiv, pickerLinkElement.firstChild);

// Attach escape key handler to return focus to the dropdown button
this.attachEscapeFocusHandler(element, false);
}

render() {
Expand Down Expand Up @@ -296,10 +370,10 @@ export class SectionPickerClass extends ComponentBase<SectionPickerState, Sectio

return (
<div id={Constants.Ids.locationPickerContainer} {...this.onElementFirstDraw(this.addSrOnlyLocationDiv)}>
<div id={Constants.Ids.optionLabel} className="optionLabel">
<label htmlFor={Constants.Ids.sectionLocationContainer} aria-label={locationString} className="buttonLabelFont" style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Regular)}>
<span aria-hidden="true">{locationString}</span>
</label>
<div id={Constants.Ids.optionLabel} className="optionLabel" aria-hidden="true">
<span className="buttonLabelFont" style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Regular)}>
{locationString}
</span>
</div>
<OneNotePicker.OneNotePickerComponent
id={Constants.Ids.sectionLocationContainer}
Expand Down
69 changes: 68 additions & 1 deletion src/scripts/clipperUI/mainController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export class MainControllerClass extends ComponentBase<MainControllerState, Main
if (event.keyCode === Constants.KeyCodes.esc) {
this.handleEscPress();
}
// Handle focus trap for success panel (A11y fix)
if (event.keyCode === Constants.KeyCodes.tab) {
this.handleFocusTrap(event);
}
};
}

Expand All @@ -97,6 +101,68 @@ export class MainControllerClass extends ComponentBase<MainControllerState, Main
}
}

// Prevents Tab key from moving focus outside the popup
handleFocusTrap(event: KeyboardEvent) {
// Only trap focus for specific panels that need it
if (this.state.currentPanel !== PanelType.ClippingSuccess &&
this.state.currentPanel !== PanelType.RegionInstructions) {
return;
}

let mainController = document.getElementById(Constants.Ids.mainController);
if (!mainController) {
return;
}

// Get all focusable elements within the main controller
let focusableElements = mainController.querySelectorAll(
"a[tabindex], button[tabindex], input[tabindex], [tabindex]:not([tabindex='-1'])"
);

if (focusableElements.length === 0) {
return;
}

// Filter to only include elements with positive tabIndex and sort by tabIndex
let sortedFocusables: HTMLElement[] = [];
for (let i = 0; i < focusableElements.length; i++) {
let element = focusableElements[i] as HTMLElement;
if (element.tabIndex >= 0) {
sortedFocusables.push(element);
}
}

if (sortedFocusables.length === 0) {
return;
}

// Sort by tabIndex
sortedFocusables.sort((a, b) => a.tabIndex - b.tabIndex);

// Always handle Tab manually to prevent focus from escaping the iframe
event.preventDefault();

// Find current element's index
let currentIndex = -1;
for (let i = 0; i < sortedFocusables.length; i++) {
if (document.activeElement === sortedFocusables[i]) {
currentIndex = i;
break;
}
}

let nextIndex: number;
if (event.shiftKey) {
// Shift + Tab: move to previous, wrap to last if at first
nextIndex = currentIndex <= 0 ? sortedFocusables.length - 1 : currentIndex - 1;
} else {
// Tab: move to next, wrap to first if at last
nextIndex = currentIndex >= sortedFocusables.length - 1 ? 0 : currentIndex + 1;
}

sortedFocusables[nextIndex].focus();
}

initAnimationStrategy() {
this.controllerAnimationStrategy = new ExpandFromRightAnimationStrategy({
extShouldAnimateIn: () => { return this.props.clipperState.uiExpanded; },
Expand Down Expand Up @@ -361,7 +427,8 @@ export class MainControllerClass extends ComponentBase<MainControllerState, Main
<div
id={Constants.Ids.mainController}
{...this.onElementDraw(this.onMainControllerDraw)}
role="main"
role="dialog"
aria-modal="true"
aria-label={Localization.getLocalizedString("WebClipper.Label.OneNoteWebClipper")} >
{closeButtonToRender}
<div className="panelContent">
Expand Down
5 changes: 5 additions & 0 deletions src/scripts/clipperUI/panels/regionSelectingPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {ClipperStateProp} from "../clipperState";
import {ComponentBase} from "../componentBase";

class RegionSelectingPanelClass extends ComponentBase<{}, ClipperStateProp> {
initiallySetFocus(element: HTMLElement) {
element.focus();
}

handleCancelButton() {
this.props.clipperState.setState({
focusOnRender: Constants.Ids.regionButton
Expand All @@ -28,6 +32,7 @@ class RegionSelectingPanelClass extends ComponentBase<{}, ClipperStateProp> {
</div>
<div className="wideButtonContainer">
<a id={ Constants.Ids.regionClipCancelButton } role="button"
{...this.onElementFirstDraw(this.initiallySetFocus)}
{...this.enableInvoke({callback: this.handleCancelButton, tabIndex: 0})} >
<span className="wideButtonFont wideActionButton buttonTextInHighContrast" style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Semibold)}>
{Localization.getLocalizedString("WebClipper.Action.BackToHome")}
Expand Down
5 changes: 5 additions & 0 deletions src/scripts/clipperUI/panels/successPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {Clipper} from "../frontEndGlobals";
import {SpriteAnimation} from "../components/spriteAnimation";

class SuccessPanelClass extends ComponentBase<{ }, ClipperStateProp> {
initiallySetFocus(element: HTMLElement) {
element.focus();
}

public onLaunchOneNoteButton() {
Clipper.logger.logUserFunnel(Log.Funnel.Label.ViewInWac);
let data = this.props.clipperState.oneNoteApiResult.data as OneNoteApi.Page;
Expand All @@ -38,6 +42,7 @@ class SuccessPanelClass extends ComponentBase<{ }, ClipperStateProp> {
</div>
<div className="wideButtonContainer">
<a id={Constants.Ids.launchOneNoteButton} className="wideButtonFont wideActionButton buttonTextInHighContrast" role="button"
{...this.onElementFirstDraw(this.initiallySetFocus)}
{...this.enableInvoke({callback: this.onLaunchOneNoteButton, tabIndex: 70})}
style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Regular)}>
{Localization.getLocalizedString("WebClipper.Action.ViewInOneNote")}
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/extensions/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"256": "icons/icon-256.png"
},
"action": {
"default_title": "Clip to OneNote",
"default_title": "OneNote Web Clipper",
"default_icon": {
"19": "icons/icon-19.png",
"38": "icons/icon-38.png"
Expand Down
Loading
Loading