mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
feat(editor): Toggle command palette closed with Cmd+F and refocus canvas
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e4aa5f0cb3
commit
33f3a16e36
|
|
@ -152,20 +152,22 @@ const scrollSelectedIntoView = () => {
|
|||
});
|
||||
};
|
||||
|
||||
// Watch instead of imperative open so parents can open via the `open` model too
|
||||
// Watch instead of imperative open/close so parents can drive the `open` model too
|
||||
watch(isOpen, async (open) => {
|
||||
if (!open) return;
|
||||
selectedIndex.value = 0;
|
||||
inputValue.value = '';
|
||||
await nextTick();
|
||||
inputRef.value?.focus();
|
||||
if (open) {
|
||||
selectedIndex.value = 0;
|
||||
inputValue.value = '';
|
||||
await nextTick();
|
||||
inputRef.value?.focus();
|
||||
} else {
|
||||
selectedIndex.value = -1;
|
||||
inputValue.value = '';
|
||||
currentParentId.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
const closeCommandBar = () => {
|
||||
isOpen.value = false;
|
||||
selectedIndex.value = -1;
|
||||
inputValue.value = '';
|
||||
currentParentId.value = null;
|
||||
};
|
||||
|
||||
const navigateToChildren = (item: CommandBarItem) => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { type MockedStore, mockedStore } from '@/__tests__/utils';
|
|||
import { defaultSettings } from '@/__tests__/defaults';
|
||||
import { useSettingsStore } from '@n8n/stores/settings.store';
|
||||
import { commandBarEventBus } from '@/features/shared/commandBar/commandBar.eventBus';
|
||||
import { canvasEventBus } from '@/features/workflows/canvas/canvas.eventBus';
|
||||
import AppCommandBar from './AppCommandBar.vue';
|
||||
|
||||
vi.mock('@/app/utils/rbac/permissions', () => ({
|
||||
|
|
@ -65,6 +66,21 @@ describe('AppCommandBar', () => {
|
|||
await waitFor(() => expect(getByTestId('command-bar-stub').dataset.open).toBe('true'));
|
||||
});
|
||||
|
||||
it('closes the command bar on Cmd+F and refocuses the canvas', async () => {
|
||||
const emitSpy = vi.spyOn(canvasEventBus, 'emit');
|
||||
const { getByTestId } = renderComponent({ pinia });
|
||||
|
||||
commandBarEventBus.emit('open:request');
|
||||
await waitFor(() => expect(getByTestId('command-bar-stub').dataset.open).toBe('true'));
|
||||
|
||||
document.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'f', metaKey: true, cancelable: true }),
|
||||
);
|
||||
|
||||
await waitFor(() => expect(getByTestId('command-bar-stub').dataset.open).toBe('false'));
|
||||
await waitFor(() => expect(emitSpy).toHaveBeenCalledWith('focus'));
|
||||
});
|
||||
|
||||
it('does not render the command bar in canvas-only mode', () => {
|
||||
settingsStore.settings = { ...defaultSettings, canvasOnly: true };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { N8nCommandBar } from '@n8n/design-system';
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { canvasEventBus } from '@/features/workflows/canvas/canvas.eventBus';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { VIEWS } from '@/app/constants';
|
||||
import { useStyles } from '@n8n/composables/useStyles';
|
||||
|
|
@ -47,8 +48,24 @@ function onOpenRequest() {
|
|||
isCommandBarOpen.value = true;
|
||||
}
|
||||
|
||||
onMounted(() => commandBarEventBus.on('open:request', onOpenRequest));
|
||||
onBeforeUnmount(() => commandBarEventBus.off('open:request', onOpenRequest));
|
||||
// Cmd+F toggles: the canvas keymap opens, but can't close while the bar's input has focus
|
||||
function onDocumentKeydown(event: KeyboardEvent) {
|
||||
if (!isCommandBarOpen.value) return;
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'f') {
|
||||
event.preventDefault();
|
||||
isCommandBarOpen.value = false;
|
||||
void nextTick(() => canvasEventBus.emit('focus'));
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
commandBarEventBus.on('open:request', onOpenRequest);
|
||||
document.addEventListener('keydown', onDocumentKeydown, { capture: true });
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
commandBarEventBus.off('open:request', onOpenRequest);
|
||||
document.removeEventListener('keydown', onDocumentKeydown, { capture: true });
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -280,6 +280,8 @@ export type CanvasEventBusEvents = {
|
|||
};
|
||||
'create:sticky': never;
|
||||
'deprecated:tab-shortcut': never;
|
||||
/** Move keyboard focus back to the canvas element */
|
||||
focus: never;
|
||||
};
|
||||
|
||||
export interface CanvasNodeInjectionData {
|
||||
|
|
|
|||
|
|
@ -233,6 +233,7 @@ const {
|
|||
addSelectedNodes,
|
||||
removeSelectedNodes,
|
||||
viewportRef,
|
||||
vueFlowRef,
|
||||
fitView,
|
||||
fitBounds,
|
||||
zoomIn,
|
||||
|
|
@ -1673,6 +1674,10 @@ function onRequestSetConnectionsOnInit(connections: IConnections) {
|
|||
pendingConnections = connections;
|
||||
}
|
||||
|
||||
function onFocusCanvas() {
|
||||
vueFlowRef.value?.focus();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.eventBus.on('fitView', onFitView);
|
||||
props.eventBus.on('fitView:onNodesInit', onRequestFitViewOnInit);
|
||||
|
|
@ -1680,6 +1685,7 @@ onMounted(() => {
|
|||
props.eventBus.on('nodes:select', onSelectNodes);
|
||||
props.eventBus.on('nodes:selectAll', onSelectAllNodes);
|
||||
props.eventBus.on('tidyUp', onTidyUp);
|
||||
props.eventBus.on('focus', onFocusCanvas);
|
||||
window.addEventListener('blur', onWindowBlur);
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
});
|
||||
|
|
@ -1691,6 +1697,7 @@ onUnmounted(() => {
|
|||
props.eventBus.off('nodes:select', onSelectNodes);
|
||||
props.eventBus.off('nodes:selectAll', onSelectAllNodes);
|
||||
props.eventBus.off('tidyUp', onTidyUp);
|
||||
props.eventBus.off('focus', onFocusCanvas);
|
||||
window.removeEventListener('blur', onWindowBlur);
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
});
|
||||
|
|
@ -1794,6 +1801,7 @@ defineExpose({
|
|||
:pan-activation-key-code="panningKeyCode"
|
||||
:disable-keyboard-a11y="true"
|
||||
:delete-key-code="null"
|
||||
tabindex="-1"
|
||||
data-test-id="canvas"
|
||||
@connect-start="onConnectStart"
|
||||
@connect="onConnect"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user