diff --git a/CHANGELOG.md b/CHANGELOG.md index 70826293..6ba88776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Preserve expanded layer groups when deleting layers, including the first layer of a group - The map's data listener now fires on tile loads again, so source and vector layer field autocompletion is populated - The `maputnik` desktop binary now opens the default browser automatically on startup (opt out with `--no-browser`) +- Keyboard shortcuts now keep working while the map has focus, instead of going dead until the map is blurred - _...Add new stuff here..._ ## 3.1.0 diff --git a/e2e/keyboard.spec.ts b/e2e/keyboard.spec.ts index 8b67f12f..7ac6bd6f 100644 --- a/e2e/keyboard.spec.ts +++ b/e2e/keyboard.spec.ts @@ -62,5 +62,22 @@ describe("keyboard", () => { await when.typeKeys("!"); await then(get.elementByTestId("modal:debug")).shouldBeVisible(); }); + + describe("while the map has focus", () => { + beforeEach(async () => { + await when.typeKeys("m"); + await then(get.canvas()).shouldBeFocused(); + }); + + test("'!' should show debug modal", async () => { + await when.typeKeys("!"); + await then(get.elementByTestId("modal:debug")).shouldBeVisible(); + }); + + test("'s' should show settings modal", async () => { + await when.typeKeys("s"); + await then(get.elementByTestId("modal:settings")).shouldBeVisible(); + }); + }); }); }); diff --git a/src/components/App.tsx b/src/components/App.tsx index 09bc13a5..2cbcdc50 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -83,6 +83,22 @@ function updateRootSpec(spec: any, fieldName: string, newValues: any) { }; } +/** + * Whether the given element consumes keystrokes as text. + * + * Shortcuts have to stay out of the way while the user is typing, but asking + * whether the focus is on `document.body` answers a different question: the map + * canvas is not a text field, yet focusing it used to disable every shortcut. + */ +function isTextEntryElement(element: Element | null): boolean { + if (!element) return false; + const node = element as HTMLElement; + return node.isContentEditable || + node.tagName === "INPUT" || + node.tagName === "TEXTAREA" || + node.tagName === "SELECT"; +} + type AppState = { errors: MappedError[], infos: string[], @@ -242,7 +258,7 @@ export class App extends React.Component { (e.target as HTMLElement).blur(); document.body.focus(); } - else if(this.state.isOpen.shortcuts || document.activeElement === document.body) { + else if(this.state.isOpen.shortcuts || !isTextEntryElement(document.activeElement)) { const shortcut = shortcuts.find((shortcut) => { return (shortcut.key === e.key); });