diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/composables/useKeyboardNavigation.ts b/packages/frontend/editor-ui/src/components/Node/NodeCreator/composables/useKeyboardNavigation.ts index 6ffe1698710..5269f9acf2c 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreator/composables/useKeyboardNavigation.ts +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/composables/useKeyboardNavigation.ts @@ -25,6 +25,29 @@ export const useKeyboardNavigation = defineStore('nodeCreatorKeyboardNavigation' // Array of objects that contains key code and handler const keysHooks = ref>({}); + function shouldAllowNativeInputBehavior(target: EventTarget | null, key: string): boolean { + // Only check for input/textarea elements + if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) { + return false; + } + + const hasContent = target.value.length > 0; + + // Allow left arrow for cursor movement when input has content + if (key === 'ArrowLeft' && hasContent) { + return true; + } + + // Allow right arrow for cursor movement only when cursor is NOT at the end + if (key === 'ArrowRight' && hasContent) { + const cursorPosition = target.selectionStart || 0; + const isAtEnd = cursorPosition >= target.value.length; + return !isAtEnd; + } + + return false; + } + function getItemType(element?: Element) { return element?.getAttribute('data-keyboard-nav-type'); } @@ -74,6 +97,12 @@ export const useKeyboardNavigation = defineStore('nodeCreatorKeyboardNavigation' const pressedKey = e.key; if (!WATCHED_KEYS.includes(pressedKey)) return; + + // Allow arrow keys for cursor movement in non-empty input fields + if (shouldAllowNativeInputBehavior(e.target, pressedKey)) { + return; + } + e.preventDefault(); e.stopPropagation(); diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts b/packages/frontend/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts index d8d3b7f9345..6fee6be95fc 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/useKeyboardNavigation.test.ts @@ -86,3 +86,117 @@ describe('useKeyboardNavigation', () => { expect(eventHookSpy).toHaveBeenCalledWith('item1', 'Escape'); }); }); + +describe('shouldAllowNativeInputBehavior', () => { + const InputTestComponent = defineComponent({ + setup() { + const { attachKeydownEvent, detachKeydownEvent } = useKeyboardNavigation(); + return { attachKeydownEvent, detachKeydownEvent }; + }, + mounted() { + this.attachKeydownEvent(); + }, + template: ` +
+ + +
+ Item 1 +
+
+ `, + }); + + const renderInputComponent = createComponentRenderer(InputTestComponent, { + pinia: createPinia(), + }); + + test('allows left/right arrows for cursor movement in non-empty input', async () => { + const { container } = renderInputComponent(); + const input = container.querySelector('#search-input') as HTMLInputElement; + + // Type some text in the input + await userEvent.click(input); + await userEvent.type(input, 'hello'); + + // Position cursor at the beginning + input.setSelectionRange(0, 0); + expect(input.selectionStart).toBe(0); + + // Right arrow should move cursor (native behavior) + await userEvent.keyboard('{arrowright}'); + expect(input.selectionStart).toBe(1); + + // Left arrow should move cursor (native behavior) + await userEvent.keyboard('{arrowleft}'); + expect(input.selectionStart).toBe(0); + }); + + test('allows left/right arrows for cursor movement in non-empty textarea', async () => { + const { container } = renderInputComponent(); + const textarea = container.querySelector('#text-area') as HTMLTextAreaElement; + + // Type some text in the textarea + await userEvent.click(textarea); + await userEvent.type(textarea, 'hello world'); + + // Position cursor at the beginning + textarea.setSelectionRange(0, 0); + expect(textarea.selectionStart).toBe(0); + + // Right arrow should move cursor (native behavior) + await userEvent.keyboard('{arrowright}'); + expect(textarea.selectionStart).toBe(1); + }); + + test('prevents left/right arrows in empty input (allows navigation)', async () => { + const { container } = renderInputComponent(); + const input = container.querySelector('#search-input') as HTMLInputElement; + + // Focus empty input + await userEvent.click(input); + expect(input.value).toBe(''); + + // Store original cursor position (should be 0) + const originalPosition = input.selectionStart; + expect(originalPosition).toBe(0); + + // Try to move cursor with right arrow - should NOT move because navigation intercepts + await userEvent.keyboard('{arrowright}'); + + // Cursor position should remain the same because preventDefault was called + expect(input.selectionStart).toBe(originalPosition); + }); + + test('allows up/down arrows for navigation even in non-empty input', async () => { + const { container } = renderInputComponent(); + const input = container.querySelector('#search-input') as HTMLInputElement; + + // Type some text in the input + await userEvent.click(input); + await userEvent.type(input, 'hello'); + + // Position cursor in the middle + input.setSelectionRange(2, 2); + const originalPosition = input.selectionStart; + + // Down arrow should trigger navigation (preventDefault), not move cursor + await userEvent.keyboard('{arrowdown}'); + + // Cursor position should remain the same because navigation took over + expect(input.selectionStart).toBe(originalPosition); + }); +});