Skip to content

Commit bf3e135

Browse files
authored
fix: avoid shortcut conflicts in CodeMirror editors (hoppscotch#5224)
Prevents `alt+up` and `alt+down` from triggering global keybindings when focus is in CodeMirror editors or other typable elements.
1 parent 0b605fe commit bf3e135

File tree

3 files changed

+97
-1
lines changed

3 files changed

+97
-1
lines changed

packages/hoppscotch-common/src/helpers/keybindings.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { onBeforeUnmount, onMounted } from "vue"
22
import { HoppActionWithOptionalArgs, invokeAction } from "./actions"
33
import { isAppleDevice } from "./platformutils"
4-
import { isDOMElement, isTypableElement } from "./utils/dom"
4+
import { isCodeMirrorEditor, isDOMElement, isTypableElement } from "./utils/dom"
55
import { getKernelMode } from "@hoppscotch/kernel"
66
import { listen } from "@tauri-apps/api/event"
77

@@ -193,6 +193,15 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
193193
return null
194194
}
195195

196+
// Restrict alt+up and alt+down when the target is a codemirror editor
197+
if (
198+
modifierKey === "alt" &&
199+
(key === "up" || key === "down") &&
200+
isCodeMirrorEditor(target)
201+
) {
202+
return null
203+
}
204+
196205
return `${modifierKey}-${key}`
197206
}
198207

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, test } from "vitest"
2+
import { isDOMElement, isTypableElement, isCodeMirrorEditor } from "../dom"
3+
4+
describe("isDOMElement", () => {
5+
test("returns true for valid HTMLElement", () => {
6+
const div = document.createElement("div")
7+
expect(isDOMElement(div)).toBe(true)
8+
})
9+
10+
test("returns false for non-HTMLElement inputs", () => {
11+
expect(isDOMElement(null)).toBe(false)
12+
expect(isDOMElement(undefined)).toBe(false)
13+
expect(isDOMElement("div")).toBe(false)
14+
expect(isDOMElement(123)).toBe(false)
15+
expect(isDOMElement({})).toBe(false)
16+
})
17+
})
18+
19+
describe("isTypableElement", () => {
20+
test("returns true for enabled <input>", () => {
21+
const input = document.createElement("input")
22+
expect(isTypableElement(input)).toBe(true)
23+
})
24+
25+
test("returns false for disabled <input>", () => {
26+
const input = document.createElement("input")
27+
input.disabled = true
28+
expect(isTypableElement(input)).toBe(false)
29+
})
30+
31+
test("returns true for enabled <textarea>", () => {
32+
const textarea = document.createElement("textarea")
33+
expect(isTypableElement(textarea)).toBe(true)
34+
})
35+
36+
test("returns false for disabled <textarea>", () => {
37+
const textarea = document.createElement("textarea")
38+
textarea.disabled = true
39+
expect(isTypableElement(textarea)).toBe(false)
40+
})
41+
42+
test("returns true for contentEditable element", () => {
43+
const div = document.createElement("div")
44+
div.isContentEditable = true
45+
expect(isTypableElement(div)).toBe(true)
46+
})
47+
48+
test("returns false for regular non-typable div", () => {
49+
const div = document.createElement("div")
50+
expect(isTypableElement(div)).toBe(false)
51+
})
52+
})
53+
54+
describe("isCodeMirrorEditor", () => {
55+
test("returns true if element is inside .cm-editor", () => {
56+
const wrapper = document.createElement("div")
57+
wrapper.classList.add("cm-editor")
58+
59+
const child = document.createElement("div")
60+
wrapper.appendChild(child)
61+
document.body.appendChild(wrapper)
62+
63+
expect(isCodeMirrorEditor(child)).toBe(true)
64+
})
65+
66+
test("returns false if element is not inside .cm-editor", () => {
67+
const div = document.createElement("div")
68+
document.body.appendChild(div)
69+
70+
expect(isCodeMirrorEditor(div)).toBe(false)
71+
})
72+
73+
test("returns false if input is not an HTMLElement", () => {
74+
expect(isCodeMirrorEditor(null)).toBe(false)
75+
expect(isCodeMirrorEditor("not-an-element")).toBe(false)
76+
})
77+
})

packages/hoppscotch-common/src/helpers/utils/dom.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,13 @@ export function isTypableElement(el: HTMLElement): boolean {
1717

1818
return false
1919
}
20+
21+
/**
22+
* Checks if an element is a CodeMirror editor.
23+
* @param el The element to check. If this is not an HTMLElement, the function will return false.
24+
* @returns True if the element is a CodeMirror editor, false otherwise.
25+
*/
26+
export function isCodeMirrorEditor(el: EventTarget | null): boolean {
27+
if (!(el instanceof HTMLElement)) return false
28+
return el.closest(".cm-editor") !== null
29+
}

0 commit comments

Comments
 (0)