From bf8aec3cd07f603e7fbdb96f0ba9a64a317be3c7 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:13:33 +0200 Subject: [PATCH] fix(core): don't swallow input keystrokes held with a modifier (#1999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vue-flow's pan/zoom/selection/multi-selection key detection acted (and `preventDefault`-ed) even when the keydown targeted a page input while a modifier was held. Since `panActivationKeyCode` defaults to `Space` and `selectionKeyCode` to `Shift`, pressing Space in an `` anywhere on the page with Shift held had its space swallowed — `useKeyPress` listens on `document`, so any input was affected. Pass `actInsideInputWithModifier: false` to those `useKeyPress` calls (the pattern already used for `deleteKeyCode`), so they defer to the focused input. Adds a regression test that types Shift+Space into an input beside the flow. Fixes #1999. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/keypress-input-modifier.md | 5 +++ packages/core/src/container/Pane/Pane.vue | 4 ++- .../core/src/container/ZoomPane/ZoomPane.vue | 9 +++-- .../2-vue-flow/keyPressInsideInput.cy.ts | 36 +++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 .changeset/keypress-input-modifier.md create mode 100644 tests/cypress/component/2-vue-flow/keyPressInsideInput.cy.ts diff --git a/.changeset/keypress-input-modifier.md b/.changeset/keypress-input-modifier.md new file mode 100644 index 000000000..f9ac1acd7 --- /dev/null +++ b/.changeset/keypress-input-modifier.md @@ -0,0 +1,5 @@ +--- +"@vue-flow/core": patch +--- + +Don't swallow keystrokes typed into page inputs while a modifier is held. The pan/zoom/selection/multi-selection key detection now passes `actInsideInputWithModifier: false` (matching `deleteKeyCode`), so e.g. pressing Space in an `` with Shift held is no longer `preventDefault`-ed by the pan-activation shortcut. Fixes #1999. diff --git a/packages/core/src/container/Pane/Pane.vue b/packages/core/src/container/Pane/Pane.vue index a72b63bd5..714ef60ec 100644 --- a/packages/core/src/container/Pane/Pane.vue +++ b/packages/core/src/container/Pane/Pane.vue @@ -51,7 +51,9 @@ let selectionStarted = false const deleteKeyPressed = useKeyPress(deleteKeyCode, { actInsideInputWithModifier: false }) -const multiSelectKeyPressed = useKeyPress(multiSelectionKeyCode) +// see `deleteKeyCode` above — don't let the multi-selection key (often Ctrl/Meta) hijack keystrokes like +// Ctrl+A/C/V while the user is typing in a page input +const multiSelectKeyPressed = useKeyPress(multiSelectionKeyCode, { actInsideInputWithModifier: false }) watch(deleteKeyPressed, (isKeyPressed) => { if (!isKeyPressed) { diff --git a/packages/core/src/container/ZoomPane/ZoomPane.vue b/packages/core/src/container/ZoomPane/ZoomPane.vue index 6227a2dd3..aa0a6003a 100644 --- a/packages/core/src/container/ZoomPane/ZoomPane.vue +++ b/packages/core/src/container/ZoomPane/ZoomPane.vue @@ -37,11 +37,14 @@ const { connectionStartHandle, } = storeToRefs(useStore()) -const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode) +// `actInsideInputWithModifier: false`: while a modifier is held, don't activate zoom/pan/selection when +// the event targets a page input — otherwise these keys (e.g. Space for pan) `preventDefault` keystrokes +// the user is typing into an input anywhere on the page. Mirrors `deleteKeyCode` in `Pane.vue`. +const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode, { actInsideInputWithModifier: false }) -const panKeyPressed = useKeyPress(panActivationKeyCode) +const panKeyPressed = useKeyPress(panActivationKeyCode, { actInsideInputWithModifier: false }) -const selectionKeyPressed = useKeyPress(selectionKeyCode) +const selectionKeyPressed = useKeyPress(selectionKeyCode, { actInsideInputWithModifier: false }) const shouldPanOnDrag = toRef(() => !selectionKeyPressed.value && (panKeyPressed.value || panOnDrag.value)) diff --git a/tests/cypress/component/2-vue-flow/keyPressInsideInput.cy.ts b/tests/cypress/component/2-vue-flow/keyPressInsideInput.cy.ts new file mode 100644 index 000000000..4333dfc14 --- /dev/null +++ b/tests/cypress/component/2-vue-flow/keyPressInsideInput.cy.ts @@ -0,0 +1,36 @@ +import { defineComponent, h, ref } from 'vue' +import { VueFlow } from '@vue-flow/core' + +// Regression for #1999: while a modifier (e.g. Shift) is held, vue-flow's pan/zoom/selection key +// detection must NOT `preventDefault` keystrokes that target a page input. `panActivationKeyCode` +// defaults to `Space` and `selectionKeyCode` to `Shift`, so Shift+Space used to be swallowed in inputs. +const App = defineComponent({ + setup() { + const text = ref('') + const nodes = ref([{ id: '1', position: { x: 0, y: 0 }, data: { label: 'n1' } }]) + + return () => + h('div', [ + h('input', { + 'data-testid': 'page-input', + 'value': text.value, + 'onInput': (e: any) => (text.value = e.target.value), + }), + h('div', { style: 'width: 300px; height: 300px' }, [ + h(VueFlow, { 'nodes': nodes.value, 'onUpdate:nodes': (v: any[]) => (nodes.value = v) }), + ]), + ]) + }, +}) + +describe('Issue #1999: key press inside a page input', () => { + it('does not swallow Space typed while Shift is held (pan key + selection modifier)', () => { + cy.mount(App) + + cy.get('[data-testid=page-input]').focus().type('a{shift} b', { delay: 20 }) + + // the input keeps every character it was given — the Shift+Space is not preventDefaulted away + cy.get('[data-testid=page-input]').invoke('val').should('contain', ' ') + cy.get('[data-testid=page-input]').invoke('val').should('have.length', 3) + }) +})