[fix] Change keyboard event handling from event.key to event.code

This fixes keyboard shortcuts not working on non-English (non-Latin) keyboard layouts.

Changes:
- KeyComboImpl.fromEvent() now uses event.code instead of event.key
- Updated isModifier check to use key codes for modifier keys
- Added getDisplayKey() method to convert key codes to readable names
- Updated Escape key handling in keybindingService to use event.code
- Updated KeybindingPanel captureKeybinding to use event.code
- Migrated all core keybindings from event.key to event.code format
- Fixed tests to mock event.code instead of event.key

Fixes #5252

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
snomiao
2025-08-30 07:02:29 +00:00
parent 7fd2dc304a
commit 0f44b5ea58
5 changed files with 132 additions and 37 deletions

View File

@@ -237,7 +237,7 @@ async function removeKeybinding(commandData: ICommandData) {
async function captureKeybinding(event: KeyboardEvent) {
// Allow the use of keyboard shortcuts when adding keyboard shortcuts
if (!event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
switch (event.key) {
switch (event.code) {
case 'Escape':
cancelEdit()
return

View File

@@ -26,58 +26,58 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: 'r'
key: 'KeyR'
},
commandId: 'Comfy.RefreshNodeDefinitions'
},
{
combo: {
key: 'q'
key: 'KeyQ'
},
commandId: 'Workspace.ToggleSidebarTab.queue'
},
{
combo: {
key: 'w'
key: 'KeyW'
},
commandId: 'Workspace.ToggleSidebarTab.workflows'
},
{
combo: {
key: 'n'
key: 'KeyN'
},
commandId: 'Workspace.ToggleSidebarTab.node-library'
},
{
combo: {
key: 'm'
key: 'KeyM'
},
commandId: 'Workspace.ToggleSidebarTab.model-library'
},
{
combo: {
key: 's',
key: 'KeyS',
ctrl: true
},
commandId: 'Comfy.SaveWorkflow'
},
{
combo: {
key: 'o',
key: 'KeyO',
ctrl: true
},
commandId: 'Comfy.OpenWorkflow'
},
{
combo: {
key: 'g',
key: 'KeyG',
ctrl: true
},
commandId: 'Comfy.Graph.GroupSelectedNodes'
},
{
combo: {
key: ',',
key: 'Comma',
ctrl: true
},
commandId: 'Comfy.ShowSettingsDialog'
@@ -85,7 +85,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
// For '=' both holding shift and not holding shift
{
combo: {
key: '=',
key: 'Equal',
alt: true
},
commandId: 'Comfy.Canvas.ZoomIn',
@@ -93,7 +93,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: '+',
key: 'Equal',
alt: true,
shift: true
},
@@ -103,7 +103,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
// For number pad '+'
{
combo: {
key: '+',
key: 'NumpadAdd',
alt: true
},
commandId: 'Comfy.Canvas.ZoomIn',
@@ -111,7 +111,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: '-',
key: 'Minus',
alt: true
},
commandId: 'Comfy.Canvas.ZoomOut',
@@ -119,21 +119,21 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: '.'
key: 'Period'
},
commandId: 'Comfy.Canvas.FitView',
targetElementId: 'graph-canvas-container'
},
{
combo: {
key: 'p'
key: 'KeyP'
},
commandId: 'Comfy.Canvas.ToggleSelected.Pin',
targetElementId: 'graph-canvas-container'
},
{
combo: {
key: 'c',
key: 'KeyC',
alt: true
},
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Collapse',
@@ -141,7 +141,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: 'b',
key: 'KeyB',
ctrl: true
},
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Bypass',
@@ -149,7 +149,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: 'm',
key: 'KeyM',
ctrl: true
},
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Mute',
@@ -157,20 +157,20 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: '`',
key: 'Backquote',
ctrl: true
},
commandId: 'Workspace.ToggleBottomPanelTab.logs-terminal'
},
{
combo: {
key: 'f'
key: 'KeyF'
},
commandId: 'Workspace.ToggleFocusMode'
},
{
combo: {
key: 'e',
key: 'KeyE',
ctrl: true,
shift: true
},
@@ -178,7 +178,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
{
combo: {
key: 'm',
key: 'KeyM',
alt: true
},
commandId: 'Comfy.Canvas.ToggleMinimap'
@@ -187,19 +187,19 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
combo: {
ctrl: true,
shift: true,
key: 'k'
key: 'KeyK'
},
commandId: 'Workspace.ToggleBottomPanel.Shortcuts'
},
{
combo: {
key: 'v'
key: 'KeyV'
},
commandId: 'Comfy.Canvas.Unlock'
},
{
combo: {
key: 'h'
key: 'KeyH'
},
commandId: 'Comfy.Canvas.Lock'
},

View File

@@ -51,7 +51,7 @@ export const useKeybindingService = () => {
if (keybinding && keybinding.targetElementId !== 'graph-canvas') {
// Special handling for Escape key - let dialogs handle it first
if (
event.key === 'Escape' &&
event.code === 'Escape' &&
!event.ctrlKey &&
!event.altKey &&
!event.metaKey
@@ -88,7 +88,7 @@ export const useKeybindingService = () => {
}
// Escape key: close the first open modal found, and all dialogs
if (event.key === 'Escape') {
if (event.code === 'Escape') {
const modals = document.querySelectorAll<HTMLElement>('.comfy-modal')
for (const modal of modals) {
const modalDisplay = window

View File

@@ -44,7 +44,7 @@ export class KeyComboImpl implements KeyCombo {
static fromEvent(event: KeyboardEvent) {
return new KeyComboImpl({
key: event.key,
key: event.code,
ctrl: event.ctrlKey || event.metaKey,
alt: event.altKey,
shift: event.shiftKey
@@ -75,7 +75,16 @@ export class KeyComboImpl implements KeyCombo {
}
get isModifier(): boolean {
return ['Control', 'Meta', 'Alt', 'Shift'].includes(this.key)
return [
'ControlLeft',
'ControlRight',
'MetaLeft',
'MetaRight',
'AltLeft',
'AltRight',
'ShiftLeft',
'ShiftRight'
].includes(this.key)
}
get modifierCount(): number {
@@ -106,9 +115,95 @@ export class KeyComboImpl implements KeyCombo {
if (this.shift) {
sequences.push('Shift')
}
sequences.push(this.key)
sequences.push(this.getDisplayKey())
return sequences
}
getDisplayKey(): string {
// Convert key codes to display names
const keyMap: Record<string, string> = {
// Letters
KeyA: 'A',
KeyB: 'B',
KeyC: 'C',
KeyD: 'D',
KeyE: 'E',
KeyF: 'F',
KeyG: 'G',
KeyH: 'H',
KeyI: 'I',
KeyJ: 'J',
KeyK: 'K',
KeyL: 'L',
KeyM: 'M',
KeyN: 'N',
KeyO: 'O',
KeyP: 'P',
KeyQ: 'Q',
KeyR: 'R',
KeyS: 'S',
KeyT: 'T',
KeyU: 'U',
KeyV: 'V',
KeyW: 'W',
KeyX: 'X',
KeyY: 'Y',
KeyZ: 'Z',
// Numbers
Digit0: '0',
Digit1: '1',
Digit2: '2',
Digit3: '3',
Digit4: '4',
Digit5: '5',
Digit6: '6',
Digit7: '7',
Digit8: '8',
Digit9: '9',
// Function keys
F1: 'F1',
F2: 'F2',
F3: 'F3',
F4: 'F4',
F5: 'F5',
F6: 'F6',
F7: 'F7',
F8: 'F8',
F9: 'F9',
F10: 'F10',
F11: 'F11',
F12: 'F12',
// Special keys
Space: 'Space',
Enter: 'Enter',
Tab: 'Tab',
Escape: 'Escape',
Backspace: 'Backspace',
Delete: 'Delete',
ArrowUp: '↑',
ArrowDown: '↓',
ArrowLeft: '←',
ArrowRight: '→',
Home: 'Home',
End: 'End',
PageUp: 'PageUp',
PageDown: 'PageDown',
Insert: 'Insert',
// Punctuation
Minus: '-',
Equal: '=',
BracketLeft: '[',
BracketRight: ']',
Backslash: '\\',
Semicolon: ';',
Quote: "'",
Backquote: '`',
Comma: ',',
Period: '.',
Slash: '/'
}
return keyMap[this.key] || this.key
}
}
export const useKeybindingStore = defineStore('keybinding', () => {

View File

@@ -66,7 +66,7 @@ describe('keybindingService - Escape key handling', () => {
it('should execute ExitSubgraph command when Escape is pressed', async () => {
const event = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
bubbles: true,
cancelable: true
})
@@ -83,7 +83,7 @@ describe('keybindingService - Escape key handling', () => {
it('should not execute command when Escape is pressed with modifiers', async () => {
const event = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
ctrlKey: true,
bubbles: true,
cancelable: true
@@ -100,7 +100,7 @@ describe('keybindingService - Escape key handling', () => {
it('should not execute command when typing in input field', async () => {
const inputElement = document.createElement('input')
const event = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
bubbles: true,
cancelable: true
})
@@ -130,7 +130,7 @@ describe('keybindingService - Escape key handling', () => {
document.body.appendChild(dialog)
const event = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
bubbles: true,
cancelable: true
})
@@ -158,7 +158,7 @@ describe('keybindingService - Escape key handling', () => {
)
const event = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
bubbles: true,
cancelable: true
})
@@ -185,7 +185,7 @@ describe('keybindingService - Escape key handling', () => {
keybindingService = useKeybindingService()
const event = new KeyboardEvent('keydown', {
key: 'Escape',
code: 'Escape',
bubbles: true,
cancelable: true
})