mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): Use native behaviour on arrow left and right in nodes panel (#18401)
This commit is contained in:
@@ -25,6 +25,29 @@ export const useKeyboardNavigation = defineStore('nodeCreatorKeyboardNavigation'
|
||||
// Array of objects that contains key code and handler
|
||||
const keysHooks = ref<Record<string, KeyHook>>({});
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -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: `
|
||||
<div>
|
||||
<input
|
||||
id="search-input"
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
data-test-id="search-input"
|
||||
/>
|
||||
<textarea
|
||||
id="text-area"
|
||||
placeholder="Enter text..."
|
||||
data-test-id="text-area"
|
||||
></textarea>
|
||||
<div
|
||||
data-keyboard-nav-id="item1"
|
||||
data-keyboard-nav-type="node"
|
||||
data-test-id="nav-item"
|
||||
>
|
||||
Item 1
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user