Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/keypress-input-modifier.md
Original file line number Diff line number Diff line change
@@ -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 `<input>` with Shift held is no longer `preventDefault`-ed by the pan-activation shortcut. Fixes #1999.
4 changes: 3 additions & 1 deletion packages/core/src/container/Pane/Pane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/container/ZoomPane/ZoomPane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
36 changes: 36 additions & 0 deletions tests/cypress/component/2-vue-flow/keyPressInsideInput.cy.ts
Original file line number Diff line number Diff line change
@@ -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<any[]>([{ 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)
})
})
Loading