diff --git a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.test.tsx b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.test.tsx index e6bf085f9ff..7fdca0d8ed0 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.test.tsx @@ -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', () => {
) - ;(container.querySelector('[contenteditable]') as HTMLElement).focus() + focusWithEditability(container.querySelector('[contenteditable]') as HTMLElement, true) + pressModK() + 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( + + +
+ {/* biome-ignore lint/a11y/noNoninteractiveTabindex: focusable stand-in for a node view inside the editor */} + +
+
+ ) + focusWithEditability(container.querySelector('span') as HTMLElement, true) pressModK() - expect(handler).toHaveBeenCalledTimes(0) + 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', () => {
) - ;(container.querySelector('[contenteditable]') as HTMLElement).focus() + focusWithEditability(container.querySelector('[contenteditable]') as HTMLElement, false) pressModK() expect(handler).toHaveBeenCalledTimes(1) }) diff --git a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx index b03ce328735..ac154f11241 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx @@ -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 } /**