mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 05:17:28 -08:00
fix(editor): Prevent keyboard shortcuts when ndv is open in new canvas (no-changelog) (#10601)
This commit is contained in:
parent
95da4d4797
commit
78f34f66c6
|
@ -73,6 +73,7 @@ const props = withDefaults(
|
||||||
controlsPosition?: PanelPosition;
|
controlsPosition?: PanelPosition;
|
||||||
eventBus?: EventBus<CanvasEventBusEvents>;
|
eventBus?: EventBus<CanvasEventBusEvents>;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
keyBindings?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
id: 'canvas',
|
id: 'canvas',
|
||||||
|
@ -81,6 +82,7 @@ const props = withDefaults(
|
||||||
controlsPosition: PanelPosition.BottomLeft,
|
controlsPosition: PanelPosition.BottomLeft,
|
||||||
eventBus: () => createEventBus(),
|
eventBus: () => createEventBus(),
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
|
keyBindings: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -101,27 +103,36 @@ const {
|
||||||
findNode,
|
findNode,
|
||||||
} = useVueFlow({ id: props.id, deleteKeyCode: null });
|
} = useVueFlow({ id: props.id, deleteKeyCode: null });
|
||||||
|
|
||||||
useKeybindings({
|
/**
|
||||||
ctrl_c: emitWithSelectedNodes((ids) => emit('copy:nodes', ids)),
|
* Key bindings
|
||||||
ctrl_x: emitWithSelectedNodes((ids) => emit('cut:nodes', ids)),
|
*/
|
||||||
'delete|backspace': emitWithSelectedNodes((ids) => emit('delete:nodes', ids)),
|
|
||||||
ctrl_d: emitWithSelectedNodes((ids) => emit('duplicate:nodes', ids)),
|
const disableKeyBindings = computed(() => !props.keyBindings);
|
||||||
d: emitWithSelectedNodes((ids) => emit('update:nodes:enabled', ids)),
|
|
||||||
p: emitWithSelectedNodes((ids) => emit('update:nodes:pin', ids, 'keyboard-shortcut')),
|
useKeybindings(
|
||||||
enter: emitWithLastSelectedNode((id) => onSetNodeActive(id)),
|
{
|
||||||
f2: emitWithLastSelectedNode((id) => emit('update:node:name', id)),
|
ctrl_c: emitWithSelectedNodes((ids) => emit('copy:nodes', ids)),
|
||||||
tab: () => emit('create:node', 'tab'),
|
ctrl_x: emitWithSelectedNodes((ids) => emit('cut:nodes', ids)),
|
||||||
shift_s: () => emit('create:sticky'),
|
'delete|backspace': emitWithSelectedNodes((ids) => emit('delete:nodes', ids)),
|
||||||
ctrl_alt_n: () => emit('create:workflow'),
|
ctrl_d: emitWithSelectedNodes((ids) => emit('duplicate:nodes', ids)),
|
||||||
ctrl_enter: () => emit('run:workflow'),
|
d: emitWithSelectedNodes((ids) => emit('update:nodes:enabled', ids)),
|
||||||
ctrl_s: () => emit('save:workflow'),
|
p: emitWithSelectedNodes((ids) => emit('update:nodes:pin', ids, 'keyboard-shortcut')),
|
||||||
ctrl_a: () => addSelectedNodes(graphNodes.value),
|
enter: emitWithLastSelectedNode((id) => onSetNodeActive(id)),
|
||||||
'+|=': async () => await onZoomIn(),
|
f2: emitWithLastSelectedNode((id) => emit('update:node:name', id)),
|
||||||
'-|_': async () => await onZoomOut(),
|
tab: () => emit('create:node', 'tab'),
|
||||||
0: async () => await onResetZoom(),
|
shift_s: () => emit('create:sticky'),
|
||||||
1: async () => await onFitView(),
|
ctrl_alt_n: () => emit('create:workflow'),
|
||||||
// @TODO implement arrow key shortcuts to modify selection
|
ctrl_enter: () => emit('run:workflow'),
|
||||||
});
|
ctrl_s: () => emit('save:workflow'),
|
||||||
|
ctrl_a: () => addSelectedNodes(graphNodes.value),
|
||||||
|
'+|=': async () => await onZoomIn(),
|
||||||
|
'-|_': async () => await onZoomOut(),
|
||||||
|
0: async () => await onResetZoom(),
|
||||||
|
1: async () => await onFitView(),
|
||||||
|
// @TODO implement arrow key shortcuts to modify selection
|
||||||
|
},
|
||||||
|
{ disabled: disableKeyBindings },
|
||||||
|
);
|
||||||
|
|
||||||
const contextMenu = useContextMenu();
|
const contextMenu = useContextMenu();
|
||||||
|
|
||||||
|
|
85
packages/editor-ui/src/composables/useKeybindings.spec.ts
Normal file
85
packages/editor-ui/src/composables/useKeybindings.spec.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { useKeybindings } from '@/composables/useKeybindings';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
describe('useKeybindings', () => {
|
||||||
|
it('should call the correct handler for a single key press', async () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const keymap = ref({ a: handler });
|
||||||
|
|
||||||
|
useKeybindings(keymap);
|
||||||
|
|
||||||
|
const event = new KeyboardEvent('keydown', { key: 'a' });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(handler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the correct handler for a combination key press', async () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const keymap = ref({ 'ctrl+a': handler });
|
||||||
|
|
||||||
|
useKeybindings(keymap);
|
||||||
|
|
||||||
|
const event = new KeyboardEvent('keydown', { key: 'a', ctrlKey: true });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(handler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call handler if key press is ignored', async () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const keymap = ref({ a: handler });
|
||||||
|
|
||||||
|
useKeybindings(keymap);
|
||||||
|
|
||||||
|
const input = document.createElement('input');
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
const event = new KeyboardEvent('keydown', { key: 'a' });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
document.body.removeChild(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call handler if disabled', async () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const keymap = ref({ a: handler });
|
||||||
|
const disabled = ref(true);
|
||||||
|
|
||||||
|
useKeybindings(keymap, { disabled });
|
||||||
|
|
||||||
|
const event = new KeyboardEvent('keydown', { key: 'a' });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize shortcut strings correctly', async () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const keymap = ref({ 'ctrl+shift+a': handler });
|
||||||
|
|
||||||
|
useKeybindings(keymap);
|
||||||
|
|
||||||
|
const event = new KeyboardEvent('keydown', { key: 'A', ctrlKey: true, shiftKey: true });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(handler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize shortcut string alternatives correctly', async () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
const keymap = ref({ 'a|b': handler });
|
||||||
|
|
||||||
|
useKeybindings(keymap);
|
||||||
|
|
||||||
|
const eventA = new KeyboardEvent('keydown', { key: 'A' });
|
||||||
|
document.dispatchEvent(eventA);
|
||||||
|
expect(handler).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const eventB = new KeyboardEvent('keydown', { key: 'B' });
|
||||||
|
document.dispatchEvent(eventB);
|
||||||
|
expect(handler).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,13 +1,21 @@
|
||||||
import { useActiveElement, useEventListener } from '@vueuse/core';
|
import { useActiveElement, useEventListener } from '@vueuse/core';
|
||||||
import { useDeviceSupport } from 'n8n-design-system';
|
import { useDeviceSupport } from 'n8n-design-system';
|
||||||
import { computed, toValue, type MaybeRefOrGetter } from 'vue';
|
import type { MaybeRef } from 'vue';
|
||||||
|
import { computed, toValue, type MaybeRefOrGetter, unref } from 'vue';
|
||||||
|
|
||||||
type KeyMap = Record<string, (event: KeyboardEvent) => void>;
|
type KeyMap = Record<string, (event: KeyboardEvent) => void>;
|
||||||
|
|
||||||
export const useKeybindings = (keymap: MaybeRefOrGetter<KeyMap>) => {
|
export const useKeybindings = (
|
||||||
|
keymap: MaybeRefOrGetter<KeyMap>,
|
||||||
|
options?: {
|
||||||
|
disabled: MaybeRef<boolean>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
const activeElement = useActiveElement();
|
const activeElement = useActiveElement();
|
||||||
const { isCtrlKeyPressed } = useDeviceSupport();
|
const { isCtrlKeyPressed } = useDeviceSupport();
|
||||||
|
|
||||||
|
const isDisabled = computed(() => unref(options?.disabled));
|
||||||
|
|
||||||
const ignoreKeyPresses = computed(() => {
|
const ignoreKeyPresses = computed(() => {
|
||||||
if (!activeElement.value) return false;
|
if (!activeElement.value) return false;
|
||||||
|
|
||||||
|
@ -60,7 +68,7 @@ export const useKeybindings = (keymap: MaybeRefOrGetter<KeyMap>) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(event: KeyboardEvent) {
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
if (ignoreKeyPresses.value) return;
|
if (ignoreKeyPresses.value || isDisabled.value) return;
|
||||||
|
|
||||||
const shortcutString = toShortcutString(event);
|
const shortcutString = toShortcutString(event);
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,10 @@ const fallbackNodes = computed<INodeUi[]>(() =>
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const keyBindingsEnabled = computed(() => {
|
||||||
|
return !ndvStore.activeNode;
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialization
|
* Initialization
|
||||||
*/
|
*/
|
||||||
|
@ -1483,6 +1487,7 @@ onDeactivated(() => {
|
||||||
:fallback-nodes="fallbackNodes"
|
:fallback-nodes="fallbackNodes"
|
||||||
:event-bus="canvasEventBus"
|
:event-bus="canvasEventBus"
|
||||||
:read-only="isCanvasReadOnly"
|
:read-only="isCanvasReadOnly"
|
||||||
|
:key-bindings="keyBindingsEnabled"
|
||||||
@update:nodes:position="onUpdateNodesPosition"
|
@update:nodes:position="onUpdateNodesPosition"
|
||||||
@update:node:position="onUpdateNodePosition"
|
@update:node:position="onUpdateNodePosition"
|
||||||
@update:node:active="onSetNodeActive"
|
@update:node:active="onSetNodeActive"
|
||||||
|
|
Loading…
Reference in a new issue