fix(global-commands): use isContentEditable for the editable guard (#5623)

* fix(global-commands): use isContentEditable for the editable guard

* chore(lint): keep focusable span in editable-guard test with biome-ignore
This commit is contained in:
Waleed
2026-07-11 22:07:16 -07:00
committed by GitHub
parent 5dec4f3168
commit b20bbdc111
2 changed files with 32 additions and 8 deletions
@@ -94,6 +94,15 @@ describe('GlobalCommandsProvider owned-shortcut yielding', () => {
})
describe('GlobalCommandsProvider editable guard', () => {
/**
* jsdom does not implement `isContentEditable`, so stub the browser's computed
* editability on the element the test focuses.
*/
function focusWithEditability(element: HTMLElement, isContentEditable: boolean) {
Object.defineProperty(element, 'isContentEditable', { value: isContentEditable })
element.focus()
}
it('skips a non-editable command when focus is in an input', () => {
const handler = vi.fn()
mount(
@@ -115,9 +124,25 @@ describe('GlobalCommandsProvider editable guard', () => {
<div contentEditable />
</GlobalCommandsProvider>
)
;(container.querySelector('[contenteditable]') as HTMLElement).focus()
focusWithEditability(container.querySelector('[contenteditable]') as HTMLElement, true)
pressModK()
expect(handler).toHaveBeenCalledTimes(0)
expect(handler).not.toHaveBeenCalled()
})
it('skips a non-editable command when focus is on a descendant of a contenteditable root', () => {
const handler = vi.fn()
mount(
<GlobalCommandsProvider>
<RegisterModKOutsideEditable handler={handler} />
<div contentEditable>
{/* biome-ignore lint/a11y/noNoninteractiveTabindex: focusable stand-in for a node view inside the editor */}
<span tabIndex={0} />
</div>
</GlobalCommandsProvider>
)
focusWithEditability(container.querySelector('span') as HTMLElement, true)
pressModK()
expect(handler).not.toHaveBeenCalled()
})
it('fires a non-editable command when focus is in a contenteditable="false" element', () => {
@@ -129,7 +154,7 @@ describe('GlobalCommandsProvider editable guard', () => {
<div contentEditable={false} tabIndex={0} />
</GlobalCommandsProvider>
)
;(container.querySelector('[contenteditable]') as HTMLElement).focus()
focusWithEditability(container.querySelector('[contenteditable]') as HTMLElement, false)
pressModK()
expect(handler).toHaveBeenCalledTimes(1)
})
@@ -95,14 +95,13 @@ function shortcutSignature(parsed: ParsedShortcut, isMac: boolean): string {
/**
* Whether `element` is an editable region for the purposes of the editable guard.
* `contenteditable="false"` (e.g. a rich-text editor in read-only mode) is not editable,
* so commands with `allowInEditable: false` still fire while such an element has focus.
* `isContentEditable` is the browser's computed editability, so focusable descendants of a
* `contenteditable` root count as editable, while `contenteditable="false"` (e.g. a rich-text
* editor in read-only mode) does not — commands with `allowInEditable: false` still fire there.
*/
function isEditableElement(element: Element | null): boolean {
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) return true
if (!(element instanceof HTMLElement)) return false
const contentEditable = element.getAttribute('contenteditable')
return contentEditable !== null && contentEditable.toLowerCase() !== 'false'
return element instanceof HTMLElement && element.isContentEditable
}
/**