Skip to content

Commit 9e1880b

Browse files
microbit-matt-hillsdonmicrobit-grace
authored andcommitted
Add global shortcuts to improve keyboard navigation
This PR adds three shortcuts to help keyboard users navigate to the editor (workspace), toolbox and trigger the hex download. Co-authored-by: Grace <145345672+microbit-grace@users.noreply.github.com>
1 parent 489df47 commit 9e1880b

File tree

5 files changed

+83
-0
lines changed

5 files changed

+83
-0
lines changed

localtypings/pxteditor.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ declare namespace pxt.editor {
716716
zoomOut(): void;
717717
resize(): void;
718718
setScale(scale: number): void;
719+
focusWorkspace(): void;
719720
}
720721

721722
export interface IFile {

pxtsim/accessibility.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,30 @@ namespace pxsim.accessibility {
88
elem.setAttribute("tabindex", "0");
99
}
1010

11+
export function getKeyboardShortcutEditorAction(e: KeyboardEvent): pxsim.SimulatorActionMessage["type"] | null {
12+
const meta = e.metaKey || e.ctrlKey;
13+
if (e.key === "e" && meta) {
14+
e.preventDefault();
15+
return "focusWorkspace"
16+
} else if (e.key === "b" && meta) {
17+
e.preventDefault();
18+
return "focusSimulator"
19+
} else if (e.key === "d" && meta) {
20+
e.preventDefault();
21+
return "webUSBDownload"
22+
}
23+
return null
24+
}
25+
26+
export function postKeyboardEvent() {
27+
document.addEventListener("keydown", (e) => {
28+
const action = getKeyboardShortcutEditorAction(e)
29+
if (action) {
30+
Runtime.postMessage({ type: action })
31+
}
32+
});
33+
}
34+
1135
export function enableKeyboardInteraction(elem: Element, handlerKeyDown?: () => void, handlerKeyUp?: () => void): void {
1236
if (handlerKeyDown) {
1337
elem.addEventListener('keydown', (e: KeyboardEvent) => {

pxtsim/embed.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ namespace pxsim {
7575
url: string;
7676
}
7777

78+
export interface SimulatorActionMessage extends SimulatorMessage {
79+
type: "toggleShortcutDoc" | "focusWorkspace" | "focusSimulator" | "webUSBDownload";
80+
}
81+
7882
export interface SimulatorStateMessage extends SimulatorMessage {
7983
type: "status";
8084
frameid?: string;

theme/common.less

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ div.simframe > iframe {
347347
top:0; left: 0; width:100%; height:100%;
348348
}
349349

350+
#boardview:focus-visible {
351+
outline: 3px solid var(--pxt-focus-border);
352+
outline-offset: 3px;
353+
}
354+
350355
.simHeadless {
351356
height: 0 !important;
352357
width: 0 !important;

webapp/src/blocks.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as dialogs from "./dialogs";
1717
import * as blocklyFieldView from "./blocklyFieldView";
1818
import { CreateFunctionDialog } from "./createFunction";
1919
import { initializeSnippetExtensions } from './snippetBuilder';
20+
import * as cmds from "./cmds"
2021

2122
import * as pxtblockly from "../../pxtblocks";
2223
import { KeyboardNavigation } from '@blockly/keyboard-experiment';
@@ -40,6 +41,7 @@ import { Measurements } from "./constants";
4041
import { flow } from "../../pxtblocks";
4142
import { HIDDEN_CLASS_NAME } from "../../pxtblocks/plugins/flyout/blockInflater";
4243
import { FlyoutButton } from "../../pxtblocks/plugins/flyout/flyoutButton";
44+
import { userPrefersDownloadFlagSet } from "./webusb";
4345

4446
interface CopyDataEntry {
4547
version: 1;
@@ -585,6 +587,53 @@ export class Editor extends toolboxeditor.ToolboxEditor {
585587
return true
586588
}
587589
});
590+
591+
const triggerEditorAction = (action: pxsim.SimulatorActionMessage["type"]) => {
592+
switch (action) {
593+
case "focusWorkspace": {
594+
this.parent.editor.focusWorkspace();
595+
return
596+
}
597+
case "focusSimulator": {
598+
// Note that pxtsim.driver.focus() isn't the same as tabbing to the sim.
599+
(document.querySelector("#boardview") as HTMLElement).focus();
600+
return
601+
}
602+
case "webUSBDownload": {
603+
(async () => {
604+
// TODO: refactor and share with editortoolbar.tsx
605+
const shouldShowPairingDialogOnDownload = pxt.appTarget.appTheme.preferWebUSBDownload
606+
&& pxt.appTarget?.compile?.webUSB
607+
&& pxt.usb.isEnabled
608+
&& !userPrefersDownloadFlagSet();
609+
if (shouldShowPairingDialogOnDownload
610+
&& !pxt.packetio.isConnected()
611+
&& !pxt.packetio.isConnecting()
612+
) {
613+
await cmds.pairAsync(true);
614+
}
615+
this.parent.compile();
616+
})();
617+
return
618+
}
619+
}
620+
}
621+
622+
const simulatorOrigins = [
623+
window.location.origin,
624+
// Simulator deployed origin.
625+
"https://trg-microbit.userpxt.io"
626+
]
627+
window.addEventListener("message", (e: MessageEvent<pxsim.SimulatorActionMessage>) => {
628+
// Listen to simulator iframe keydown post messages.
629+
if (simulatorOrigins.includes(e.origin)) {
630+
triggerEditorAction(e.data.type)
631+
}
632+
}, false)
633+
document.addEventListener("keydown", (e: KeyboardEvent) => {
634+
const action = pxsim.accessibility.getKeyboardShortcutEditorAction(e)
635+
triggerEditorAction(action)
636+
});
588637
}
589638
}
590639

0 commit comments

Comments
 (0)