From 93d6ff388938cb9ab08f033206249f8cbfc9514a Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 8 Sep 2026 08:47:27 +0000 Subject: [PATCH] feat: support keyboard movement for workflow nodes and comments (#41967) --- .../keyboard-node-movement.browser.spec.tsx | 162 ++++++++++++++ .../__tests__/comment-thread-focus.spec.tsx | 136 ++++++++++++ .../workflow/comment/comment-icon.spec.tsx | 57 +++++ .../workflow/comment/comment-icon.tsx | 65 +++++- .../workflow/comment/comment-input.spec.tsx | 49 +++++ .../workflow/comment/comment-input.tsx | 42 +++- .../comment/comment-movement.browser.spec.tsx | 118 ++++++++++ .../components/workflow/comment/thread.tsx | 8 +- .../use-node-keyboard-interactions.spec.tsx | 201 ++++++++++++++++++ .../hooks/use-node-keyboard-interactions.ts | 123 +++++++++++ web/app/components/workflow/index.tsx | 4 + .../components/workflow/nodes/_base/node.tsx | 1 + web/app/components/workflow/style.css | 6 + .../workflow/utils/keyboard-movement.ts | 18 ++ web/i18n/ar-TN/workflow.json | 6 + web/i18n/de-DE/workflow.json | 6 + web/i18n/en-US/workflow.json | 6 + web/i18n/es-ES/workflow.json | 6 + web/i18n/fa-IR/workflow.json | 6 + web/i18n/fr-FR/workflow.json | 6 + web/i18n/hi-IN/workflow.json | 6 + web/i18n/id-ID/workflow.json | 6 + web/i18n/it-IT/workflow.json | 6 + web/i18n/ja-JP/workflow.json | 6 + web/i18n/ko-KR/workflow.json | 6 + web/i18n/lo-LA/workflow.json | 6 + web/i18n/nl-NL/workflow.json | 6 + web/i18n/pl-PL/workflow.json | 6 + web/i18n/pt-BR/workflow.json | 6 + web/i18n/ro-RO/workflow.json | 6 + web/i18n/ru-RU/workflow.json | 6 + web/i18n/sl-SI/workflow.json | 6 + web/i18n/th-TH/workflow.json | 6 + web/i18n/tr-TR/workflow.json | 6 + web/i18n/uk-UA/workflow.json | 6 + web/i18n/vi-VN/workflow.json | 6 + web/i18n/zh-Hans/workflow.json | 6 + web/i18n/zh-Hant/workflow.json | 6 + web/vite.config.ts | 6 +- 39 files changed, 1123 insertions(+), 17 deletions(-) create mode 100644 web/app/components/workflow/__tests__/keyboard-node-movement.browser.spec.tsx create mode 100644 web/app/components/workflow/comment/__tests__/comment-thread-focus.spec.tsx create mode 100644 web/app/components/workflow/comment/comment-movement.browser.spec.tsx create mode 100644 web/app/components/workflow/hooks/__tests__/use-node-keyboard-interactions.spec.tsx create mode 100644 web/app/components/workflow/hooks/use-node-keyboard-interactions.ts create mode 100644 web/app/components/workflow/utils/keyboard-movement.ts diff --git a/web/app/components/workflow/__tests__/keyboard-node-movement.browser.spec.tsx b/web/app/components/workflow/__tests__/keyboard-node-movement.browser.spec.tsx new file mode 100644 index 00000000000..92f1284f4ab --- /dev/null +++ b/web/app/components/workflow/__tests__/keyboard-node-movement.browser.spec.tsx @@ -0,0 +1,162 @@ +import type { NodeProps } from 'reactflow' +import { useState } from 'react' +import ReactFlow, { Handle, Position, ReactFlowProvider, useStoreApi } from 'reactflow' +import { page, userEvent } from 'vite-plus/test/browser' +import { render } from 'vitest-browser-react' +import { WorkflowContext } from '../context' +import { useNodeKeyboardInteractions } from '../hooks/use-node-keyboard-interactions' +import { createWorkflowStore } from '../store/workflow' +import 'reactflow/dist/style.css' +import '../style.css' + +vi.mock('../hooks/use-workflow', () => ({ + useNodesReadOnly: () => ({ getNodesReadOnly: () => false }), +})) +vi.mock('../hooks/use-nodes-sync-draft', () => ({ + useNodesSyncDraft: () => ({ handleSyncWorkflowDraft: vi.fn() }), +})) +vi.mock('../hooks/use-workflow-history', () => ({ + WorkflowHistoryEvent: { NodeDragStop: 'NodeDragStop' }, + useWorkflowHistory: () => ({ saveStateToHistory: vi.fn() }), +})) +vi.mock('../collaboration/core/collaboration-manager', () => ({ + collaborationManager: { + setNodes: vi.fn(), + setEdges: vi.fn(), + canApplyLocalGraphMutation: () => true, + }, +})) + +const nodes = [ + { + id: 'node', + type: 'test', + ariaLabel: 'Code', + position: { x: 100, y: 100 }, + data: { title: 'Code' }, + }, + { + id: 'output', + type: 'test', + ariaLabel: 'Output', + position: { x: 500, y: 200 }, + data: { title: 'Output' }, + }, +] +const edges = [{ id: 'connection', source: 'node', target: 'output', type: 'straight' }] +function TestNode({ id, data }: NodeProps) { + const store = useStoreApi() + return ( +
+ + +