Skip to content

Commit b20bbdc

Browse files
authored
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
1 parent 5dec4f3 commit b20bbdc

2 files changed

Lines changed: 32 additions & 8 deletions

File tree

apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.test.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ describe('GlobalCommandsProvider owned-shortcut yielding', () => {
9494
})
9595

9696
describe('GlobalCommandsProvider editable guard', () => {
97+
/**
98+
* jsdom does not implement `isContentEditable`, so stub the browser's computed
99+
* editability on the element the test focuses.
100+
*/
101+
function focusWithEditability(element: HTMLElement, isContentEditable: boolean) {
102+
Object.defineProperty(element, 'isContentEditable', { value: isContentEditable })
103+
element.focus()
104+
}
105+
97106
it('skips a non-editable command when focus is in an input', () => {
98107
const handler = vi.fn()
99108
mount(
@@ -115,9 +124,25 @@ describe('GlobalCommandsProvider editable guard', () => {
115124
<div contentEditable />
116125
</GlobalCommandsProvider>
117126
)
118-
;(container.querySelector('[contenteditable]') as HTMLElement).focus()
127+
focusWithEditability(container.querySelector('[contenteditable]') as HTMLElement, true)
128+
pressModK()
129+
expect(handler).not.toHaveBeenCalled()
130+
})
131+
132+
it('skips a non-editable command when focus is on a descendant of a contenteditable root', () => {
133+
const handler = vi.fn()
134+
mount(
135+
<GlobalCommandsProvider>
136+
<RegisterModKOutsideEditable handler={handler} />
137+
<div contentEditable>
138+
{/* biome-ignore lint/a11y/noNoninteractiveTabindex: focusable stand-in for a node view inside the editor */}
139+
<span tabIndex={0} />
140+
</div>
141+
</GlobalCommandsProvider>
142+
)
143+
focusWithEditability(container.querySelector('span') as HTMLElement, true)
119144
pressModK()
120-
expect(handler).toHaveBeenCalledTimes(0)
145+
expect(handler).not.toHaveBeenCalled()
121146
})
122147

123148
it('fires a non-editable command when focus is in a contenteditable="false" element', () => {
@@ -129,7 +154,7 @@ describe('GlobalCommandsProvider editable guard', () => {
129154
<div contentEditable={false} tabIndex={0} />
130155
</GlobalCommandsProvider>
131156
)
132-
;(container.querySelector('[contenteditable]') as HTMLElement).focus()
157+
focusWithEditability(container.querySelector('[contenteditable]') as HTMLElement, false)
133158
pressModK()
134159
expect(handler).toHaveBeenCalledTimes(1)
135160
})

apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,13 @@ function shortcutSignature(parsed: ParsedShortcut, isMac: boolean): string {
9595

9696
/**
9797
* Whether `element` is an editable region for the purposes of the editable guard.
98-
* `contenteditable="false"` (e.g. a rich-text editor in read-only mode) is not editable,
99-
* so commands with `allowInEditable: false` still fire while such an element has focus.
98+
* `isContentEditable` is the browser's computed editability, so focusable descendants of a
99+
* `contenteditable` root count as editable, while `contenteditable="false"` (e.g. a rich-text
100+
* editor in read-only mode) does not — commands with `allowInEditable: false` still fire there.
100101
*/
101102
function isEditableElement(element: Element | null): boolean {
102103
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) return true
103-
if (!(element instanceof HTMLElement)) return false
104-
const contentEditable = element.getAttribute('contenteditable')
105-
return contentEditable !== null && contentEditable.toLowerCase() !== 'false'
104+
return element instanceof HTMLElement && element.isContentEditable
106105
}
107106

108107
/**

0 commit comments

Comments
 (0)