Explorar el Código

feat: free-layout support multi inputs (#111)

* docs: update getting-started docs

* feat: free-layout support multi inputs
xiamidaxia hace 9 meses
padre
commit
d32fa9bf31

+ 71 - 64
apps/docs/src/en/guide/getting-started/create-fixed-layout-simple.mdx

@@ -1,7 +1,6 @@
+# Creating a Fixed Layout Canvas
 
-# Create a Fixed Layout Canvas
-
-This case can be installed by `npx @flowgram.ai/create-app@latest fixed-layout-simple`, the complete code and effect are as follows:
+This example can be installed using `npx @flowgram.ai/create-app@latest fixed-layout-simple`. For complete code and demo, see:
 
 <div className="rs-tip">
   <a className="rs-link" href="/en/examples/fixed-layout/fixed-layout-simple.html">
@@ -9,21 +8,34 @@ This case can be installed by `npx @flowgram.ai/create-app@latest fixed-layout-s
   </a>
 </div>
 
+File structure:
+
+```
+- hooks
+  - use-editor-props.ts # Canvas configuration
+- components
+  - base-node.tsx # Node rendering
+  - tools.tsx # Canvas toolbar
+- initial-data.ts # Initialization data
+- node-registries.ts # Node configuration
+- app.tsx # Canvas entry
+```
 
 ### 1. Canvas Entry
 
-- `FixedLayoutEditorProvider`: Canvas configurator, internally generates a react-context for consumption by child components
-- `EditorRenderer`: The final rendered canvas, can be wrapped in other components to customize the canvas position
+- `FixedLayoutEditorProvider`: Canvas configurator that generates react-context internally for child components to consume
+- `EditorRenderer`: The final rendered canvas that can be wrapped under other components for customizing canvas position
 
 ```tsx pure title="app.tsx"
-
 import {
   FixedLayoutEditorProvider,
   EditorRenderer,
 } from '@flowgram.ai/fixed-layout-editor';
 
-import { useEditorProps } from './use-editor-props' // Canvas detailed props configuration
-import { Tools } from './tools' // Canvas tools
+import '@flowgram.ai/fixed-layout-editor/index.css'; // Load styles
+
+import { useEditorProps } from './hooks/use-editor-props' // Detailed canvas props configuration
+import { Tools } from './components/tools' // Canvas tools
 
 function App() {
   const editorProps = useEditorProps()
@@ -38,15 +50,15 @@ function App() {
 
 ### 2. Configure Canvas
 
-Canvas configuration uses declarative, providing data, rendering, event, plugin related configurations
+Canvas configuration is declarative, providing configurations for data, rendering, events, and plugins.
 
-```tsx pure title="use-editor-props.tsx"
+```tsx pure title="hooks/use-editor-props.tsx"
 import { useMemo } from 'react';
 import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
 import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
 import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
 
-import { intialData } from './initial-data' // Initial data
+import { initialData } from './initial-data' // Initialization data
 import { nodeRegistries } from './node-registries' // Node declaration configuration
 import { BaseNode } from './base-node' // Node rendering
 
@@ -55,15 +67,16 @@ export function useEditorProps(
   return useMemo<FixedLayoutProps>(
     () => ({
       /**
-       * Initial data
+       * Initialization data
        */
       initialData,
       /**
-       * Canvas node definition
+       * Canvas node definitions
        */
       nodeRegistries,
       /**
-       * Custom UI components can be defined by key, for example, add a button, here is a semi-component set for quick verification, if you need deep customization, refer to:
+       * UI components can be customized through keys. Here, a set of semi components is provided for quick validation.
+       * For deep customization, refer to:
        * https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
        */
       materials: {
@@ -75,31 +88,31 @@ export function useEditorProps(
         renderDefaultNode: BaseNode, // Node rendering component
       },
       /**
-       * Node engine, used to render node form
+       * Node engine for rendering node forms
        */
       nodeEngine: {
         enable: true,
       },
       /**
-       * Canvas history, used to control redo/undo
+       * Canvas history record for controlling redo/undo
        */
       history: {
         enable: true,
-        enableChangeNode: true, // Used to listen to node form data changes
+        enableChangeNode: true, // Monitor node form data changes
       },
       /**
        * Canvas initialization callback
        */
       onInit: ctx => {
-        // If you want to dynamically load data, you can execute it asynchronously by the following method
+        // For dynamic data loading, use the following method asynchronously
         // ctx.docuemnt.fromJSON(initialData)
       },
       /**
-       * Canvas first rendering completed callback
+       * Callback when canvas first render completes
        */
       onAllLayersRendered: (ctx) => {},
       /**
-       * Canvas destruction callback
+       * Canvas disposal callback
        */
       onDispose: () => { },
       plugins: () => [
@@ -112,28 +125,25 @@ export function useEditorProps(
     [],
   );
 }
-
 ```
 
-
 ### 3. Configure Data
 
-Canvas document data uses a tree structure, supports nesting
+Canvas document data uses a tree structure that supports nesting.
 
-:::note Document data basic structure:
+:::note Document Data Basic Structure:
 
 - nodes `array` Node list, supports nesting
 
 :::
 
-:::note Node data basic structure:
+:::note Node Data Basic Structure:
 
-
-- id: `string` Node unique identifier, must be unique
-- meta: `object` Node ui configuration information, such as free layout `position` information
-- type: `string | number` Node type, will correspond to `type` in `nodeRegistries`
+- id: `string` Unique node identifier, must be unique
+- meta: `object` Node UI configuration information, such as position information for free layout
+- type: `string | number` Node type, corresponds to `type` in `nodeRegistries`
 - data: `object` Node form data
-- blocks: `array` Node branch, using `block` is more in line with `Gramming`
+- blocks: `array` Node branches, using `block` is closer to `Gramming`
 
 :::
 
@@ -141,7 +151,7 @@ Canvas document data uses a tree structure, supports nesting
 import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
 
 /**
- * Configure workflow data, data is in the format of nested blocks
+ * Configure flow data, data is in blocks nested format
  */
 export const initialData: FlowDocumentJSON = {
   nodes: [
@@ -155,7 +165,7 @@ export const initialData: FlowDocumentJSON = {
       },
       blocks: [],
     },
-    // Branch node
+    // Condition node
     {
       id: 'condition_0',
       type: 'condition',
@@ -176,7 +186,7 @@ export const initialData: FlowDocumentJSON = {
               type: 'custom',
               data: {
                 title: 'Custom',
-                content: 'custrom content'
+                content: 'custom content'
               },
             },
           ],
@@ -203,12 +213,11 @@ export const initialData: FlowDocumentJSON = {
     },
   ],
 };
-
 ```
 
-### 4. Declare Node
+### 4. Declare Nodes
 
-Declare node can be used to determine the type and rendering method of the node
+Node declaration can be used to determine node types and rendering methods.
 
 ```tsx pure title="node-registries.tsx"
 import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
@@ -223,30 +232,30 @@ export const nodeRegistries: FlowNodeRegistry[] = [
      */
     type: 'condition',
     /**
-     * Custom node extension:
-     *  - loop: Extended to loop node
-     *  - start: Extended to start node
-     *  - dynamicSplit: Extended to branch node
-     *  - end: Extended to end node
-     *  - tryCatch: Extended to tryCatch node
-     *  - default: Extended to normal node (default)
+     * Custom node extensions:
+     *  - loop: Extend as loop node
+     *  - start: Extend as start node
+     *  - dynamicSplit: Extend as branch node
+     *  - end: Extend as end node
+     *  - tryCatch: Extend as tryCatch node
+     *  - default: Extend as normal node (default)
      */
     extend: 'dynamicSplit',
     /**
      * Node configuration information
      */
     meta: {
-      // isStart: false, // Whether it is a start node
-      // isNodeEnd: false, // Whether it is an end node, the node after the end node cannot be added
-      // draggable: false, // Whether it can be dragged, such as the start node and the end node cannot be dragged
-      // selectable: false, // The trigger start node cannot be selected
+      // isStart: false, // Whether it's a start node
+      // isNodeEnd: false, // Whether it's an end node, no nodes can be added after end node
+      // draggable: false, // Whether draggable, start and end nodes cannot be dragged
+      // selectable: false, // Triggers and start nodes cannot be box-selected
       // deleteDisable: true, // Disable deletion
-      // copyDisable: true, // Disable copy
-      // addDisable: true, // Disable addition
+      // copyDisable: true, // Disable copying
+      // addDisable: true, // Disable adding
     },
     /**
-     * Configure node form validation and rendering,
-     * Note: validate uses data and rendering separation, ensuring that even if the node is not rendered, the data can be validated
+     * Configure node form validation and rendering
+     * Note: validate uses data and rendering separation to ensure node validation even without rendering
      */
     formMeta: {
       validateTrigger: ValidateTrigger.onChange,
@@ -269,22 +278,22 @@ export const nodeRegistries: FlowNodeRegistry[] = [
     },
   },
 ];
-
 ```
-### 5. Render Node
 
-The rendering node is used to add styles, events, and form rendering positions
+### 5. Render Nodes
+
+Node rendering is used to add styles, events, and form rendering positions.
 
-```tsx pure title="base-node.tsx"
+```tsx pure title="components/base-node.tsx"
 import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
 
 export const BaseNode = () => {
   /**
-   * Provides methods related to node rendering
+   * Provides node rendering related methods
    */
   const nodeRender = useNodeRender();
   /**
-   * Forms can only be used when the node engine is enabled
+   * Form can only be used when node engine is enabled
    */
   const form = nodeRender.form;
 
@@ -305,21 +314,19 @@ export const BaseNode = () => {
       }}
     >
       {
-        // Form rendering is generated through formMeta
+        // Form rendering generated through formMeta
         form?.render()
       }
     </div>
   );
 };
-
 ```
 
-
 ### 6. Add Tools
 
-Tools are mainly used to control canvas zooming and other operations. Tools are consolidated in `usePlaygroundTools`, while `useClientContext` is used to get the canvas context, which contains core modules such as `history`
+Tools are mainly used to control canvas zooming and other operations. Tools are collected in `usePlaygroundTools`, while `useClientContext` is used to get canvas context, which contains core modules like `history`.
 
-```tsx pure title="tools.tsx"
+```tsx pure title="components/tools.tsx"
 import { useEffect, useState } from 'react'
 import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
 
@@ -347,9 +354,9 @@ export function Tools() {
     <span>{Math.floor(tools.zoom * 100)}%</span>
   </div>
 }
-
 ```
-### 7. Effect
+
+### 7. Result
 
 import { FixedLayoutSimple } from '../../../../components';
 

+ 51 - 42
apps/docs/src/en/guide/getting-started/create-free-layout-simple.mdx

@@ -1,6 +1,6 @@
-# Create a Free Layout Canvas
+# Creating a Free Layout Canvas
 
-This case can be installed via `npx @flowgram.ai/create-app@latest free-layout-simple`. For complete code and effects, see:
+This example can be installed via `npx @flowgram.ai/create-app@latest free-layout-simple`. For complete code and demo, see:
 
 <div className="rs-tip">
   <a className="rs-link" href="/en/examples/free-layout/free-layout-simple.html">
@@ -8,20 +8,34 @@ This case can be installed via `npx @flowgram.ai/create-app@latest free-layout-s
   </a>
 </div>
 
+File structure:
+
+```
+- hooks
+  - use-editor-props.ts # Canvas configuration
+- components
+  - base-node.tsx # Node rendering
+  - tools.tsx # Canvas toolbar
+- initial-data.ts # Initialization data
+- node-registries.ts # Node configuration
+- app.tsx # Canvas entry
+```
+
 ### 1. Canvas Entry
 
-- `FreeLayoutEditorProvider`: Canvas configurator that generates react-context internally for child component consumption
-- `EditorRenderer`: The final rendered canvas that can be wrapped under other components for convenient canvas positioning
+- `FreeLayoutEditorProvider`: Canvas configurator that generates react-context internally for child components to consume
+- `EditorRenderer`: The final rendered canvas that can be wrapped under other components for customizing canvas position
 
 ```tsx pure title="app.tsx"
-
 import {
   FreeLayoutEditorProvider,
   EditorRenderer,
 } from '@flowgram.ai/free-layout-editor';
 
-import { useEditorProps } from './use-editor-props' // Detailed canvas props configuration
-import { Tools } from './tools' // Canvas tools
+import '@flowgram.ai/free-layout-editor/index.css'; // Load styles
+
+import { useEditorProps } from './hooks/use-editor-props' // Detailed canvas props configuration
+import { Tools } from './components/tools' // Canvas tools
 
 function App() {
   const editorProps = useEditorProps()
@@ -36,23 +50,23 @@ function App() {
 
 ### 2. Configure Canvas
 
-Canvas configuration is declarative, providing data, rendering, event, and plugin-related configurations
+Canvas configuration is declarative, providing data, rendering, events, and plugin-related configurations
 
-```tsx pure title="use-editor-props.tsx"
+```tsx pure title="hooks/use-editor-props.tsx"
 import { useMemo } from 'react';
 import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
 import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
 
-import { intialData } from './initial-data' // 初始化数据
-import { nodeRegistries } from './node-registries' // 节点声明配置
-import { BaseNode } from './base-node' // 节点渲染
+import { initialData } from './initial-data' // Initialization data
+import { nodeRegistries } from './node-registries' // Node declaration configuration
+import { BaseNode } from './components/base-node' // Node rendering
 
 export function useEditorProps(
 ): FreeLayoutProps {
   return useMemo<FreeLayoutProps>(
     () => ({
       /**
-       * Initialize data
+       * Initialization data
        */
       initialData,
       /**
@@ -66,23 +80,23 @@ export function useEditorProps(
         renderDefaultNode: BaseNode, // Node rendering component
       },
       /**
-       * Node engine, used for rendering node forms
+       * Node engine for rendering node forms
        */
       nodeEngine: {
         enable: true,
       },
       /**
-       * Canvas history record, used to control redo/undo
+       * Canvas history record for controlling redo/undo
        */
       history: {
         enable: true,
-        enableChangeNode: true, // Used to monitor node form data changes
+        enableChangeNode: true, // For monitoring node form data changes
       },
       /**
        * Canvas initialization callback
        */
       onInit: ctx => {
-        // If you need to load data dynamically, you can execute asynchronously using the following method
+        // For dynamic data loading, you can use the following method asynchronously
         // ctx.docuemnt.fromJSON(initialData)
       },
       /**
@@ -103,12 +117,11 @@ export function useEditorProps(
     [],
   );
 }
-
 ```
 
 ### 3. Configure Data
 
-Canvas document data uses a tree structure and supports nesting
+Canvas document data uses a tree structure that supports nesting
 
 :::note Document Data Basic Structure:
 
@@ -119,10 +132,10 @@ Canvas document data uses a tree structure and supports nesting
 
 :::note Node Data Basic Structure:
 
-- id: `string` Unique node identifier, must ensure uniqueness
-- meta: `object` Node UI configuration information, such as free layout `position` information goes here
+- id: `string` Unique node identifier, must be unique
+- meta: `object` Node UI configuration information, such as free layout `position` information
 - type: `string | number` Node type, corresponds to `type` in `nodeRegistries`
-- data: `object` Node form data, business can customize
+- data: `object` Node form data, customizable by business
 - blocks: `array` Node branches, using `block` is closer to `Gramming`
 
 :::
@@ -136,8 +149,7 @@ Canvas document data uses a tree structure and supports nesting
 
 :::
 
-
-```tsx pure title="initial-data.tsx"
+```tsx pure title="initial-data.ts"
 import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
 
 export const initialData: WorkflowJSON = {
@@ -187,13 +199,11 @@ export const initialData: WorkflowJSON = {
     },
   ],
 };
-
-
 ```
 
 ### 4. Declare Nodes
 
-Node declaration can be used to determine node types and rendering methods
+Node declarations can be used to determine node types and rendering methods
 
 ```tsx pure title="node-registries.tsx"
 import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
@@ -208,12 +218,12 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
       isStart: true, // Mark as start node
       deleteDisable: true, // Start node cannot be deleted
       copyDisable: true, // Start node cannot be copied
-      defaultPorts: [{ type: 'output' }], // Used to define node input and output ports, start node only has output port
-      // dynamicPort: true, // Used for dynamic ports, will look for DOM elements with data-port-id and data-port-type attributes as ports
+      defaultPorts: [{ type: 'output' }], // Define node input and output ports, start node only has output port
+      // dynamicPort: true, // For dynamic ports, will look for DOM with data-port-id and data-port-type attributes as ports
     },
     /**
-     * Configure node form validation and rendering,
-     * Note: validate uses data and rendering separation to ensure nodes can validate data even without rendering
+     * Configure node form validation and rendering
+     * Note: validate uses data and rendering separation to ensure node validation even without rendering
      */
     formMeta: {
       validateTrigger: ValidateTrigger.onChange,
@@ -253,16 +263,16 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
     formMeta: {
       // ...
     },
-    defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
+    defaultPorts: [{ type: 'output' }, { type: 'input' }], // Normal nodes have two ports
   },
 ];
-
 ```
+
 ### 5. Render Nodes
 
-Rendering nodes is used for adding styles, events, and form rendering positions
+Node rendering is used to add styles, events, and form rendering positions
 
-```tsx pure title="base-node.tsx"
+```tsx pure title="components/base-node.tsx"
 import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor';
 
 export const BaseNode = () => {
@@ -271,27 +281,25 @@ export const BaseNode = () => {
    */
   const { form } = useNodeRender()
   /**
-   * WorkflowNodeRenderer will add node drag events and port rendering, for deep customization,
-   * you can check the component source code at:
+   * WorkflowNodeRenderer adds node drag events and port rendering, for deep customization, see the component source code:
    * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
    */
   return (
     <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
       {
-        // Form rendering is generated through formMeta
+        // Form rendering generated through formMeta
         form?.render()
       }
     </WorkflowNodeRenderer>
   )
 };
-
 ```
 
 ### 6. Add Tools
 
-Tools are mainly used to control canvas zooming and other operations. Tools are consolidated in `usePlaygroundTools`, while `useClientContext` is used to get the canvas context, which contains core modules such as `history`
+Tools are mainly used to control canvas zoom and other operations. Tools are collected in `usePlaygroundTools`, while `useClientContext` is used to get canvas context, which contains core canvas modules like `history`
 
-```tsx pure title="tools.tsx"
+```tsx pure title="components/tools.tsx"
 import { useEffect, useState } from 'react'
 import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
 
@@ -320,7 +328,8 @@ export function Tools() {
   </div>
 }
 ```
-### 7. Effect
+
+### 7. Result
 
 import { FreeLayoutSimple } from '../../../../components';
 

+ 20 - 6
apps/docs/src/zh/guide/getting-started/create-fixed-layout-simple.mdx

@@ -9,6 +9,18 @@
   </a>
 </div>
 
+文件结构:
+
+```
+- hooks
+  - use-editor-props.ts # 画布配置
+- components
+  - base-node.tsx # 节点渲染
+  - tools.tsx # 画布工具栏
+- initial-data.ts # 初始化数据
+- node-registries.ts # 节点配置
+- app.tsx # 画布入口
+```
 
 ### 1. 画布入口
 
@@ -22,8 +34,10 @@ import {
   EditorRenderer,
 } from '@flowgram.ai/fixed-layout-editor';
 
-import { useEditorProps } from './use-editor-props' // 画布详细的 props 配置
-import { Tools } from './tools' // 画布工具
+import '@flowgram.ai/fixed-layout-editor/index.css'; // 加载样式
+
+import { useEditorProps } from './hooks/use-editor-props' // 画布详细的 props 配置
+import { Tools } from './components/tools' // 画布工具
 
 function App() {
   const editorProps = useEditorProps()
@@ -40,13 +54,13 @@ function App() {
 
 画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
 
-```tsx pure title="use-editor-props.tsx"
+```tsx pure title="hooks/use-editor-props.tsx"
 import { useMemo } from 'react';
 import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
 import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
 import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
 
-import { intialData } from './initial-data' // 初始化数据
+import { initialData } from './initial-data' // 初始化数据
 import { nodeRegistries } from './node-registries' // 节点声明配置
 import { BaseNode } from './base-node' // 节点渲染
 
@@ -275,7 +289,7 @@ export const nodeRegistries: FlowNodeRegistry[] = [
 
 渲染节点用于添加样式、事件及表单渲染的位置
 
-```tsx pure title="base-node.tsx"
+```tsx pure title="components/base-node.tsx"
 import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
 
 export const BaseNode = () => {
@@ -319,7 +333,7 @@ export const BaseNode = () => {
 
 工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
 
-```tsx pure title="tools.tsx"
+```tsx pure title="components/tools.tsx"
 import { useEffect, useState } from 'react'
 import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
 

+ 22 - 7
apps/docs/src/zh/guide/getting-started/create-free-layout-simple.mdx

@@ -10,6 +10,19 @@
 </div>
 
 
+文件结构:
+
+```
+- hooks
+  - use-editor-props.ts # 画布配置
+- components
+  - base-node.tsx # 节点渲染
+  - tools.tsx # 画布工具栏
+- initial-data.ts # 初始化数据
+- node-registries.ts # 节点配置
+- app.tsx # 画布入口
+```
+
 ### 1. 画布入口
 
 - `FreeLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
@@ -22,8 +35,10 @@ import {
   EditorRenderer,
 } from '@flowgram.ai/free-layout-editor';
 
+import '@flowgram.ai/free-layout-editor/index.css'; // 加载样式
+
 import { useEditorProps } from './use-editor-props' // 画布详细的 props 配置
-import { Tools } from './tools' // 画布工具
+import { Tools } from './components/tools' // 画布工具
 
 function App() {
   const editorProps = useEditorProps()
@@ -40,14 +55,14 @@ function App() {
 
 画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
 
-```tsx pure title="use-editor-props.tsx"
+```tsx pure title="hooks/use-editor-props.tsx"
 import { useMemo } from 'react';
 import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
 import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
 
-import { intialData } from './initial-data' // 初始化数据
+import { initialData } from './initial-data' // 初始化数据
 import { nodeRegistries } from './node-registries' // 节点声明配置
-import { BaseNode } from './base-node' // 节点渲染
+import { BaseNode } from './components/base-node' // 节点渲染
 
 export function useEditorProps(
 ): FreeLayoutProps {
@@ -140,7 +155,7 @@ export function useEditorProps(
 :::
 
 
-```tsx pure title="initial-data.tsx"
+```tsx pure title="initial-data.ts"
 import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
 
 export const initialData: WorkflowJSON = {
@@ -266,7 +281,7 @@ export const nodeRegistries: WorkflowNodeRegistry[] = [
 
 渲染节点用于添加样式、事件及表单渲染的位置
 
-```tsx pure title="base-node.tsx"
+```tsx pure title="components/base-node.tsx"
 import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor';
 
 export const BaseNode = () => {
@@ -295,7 +310,7 @@ export const BaseNode = () => {
 
 工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
 
-```tsx pure title="tools.tsx"
+```tsx pure title="components/tools.tsx"
 import { useEffect, useState } from 'react'
 import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
 

+ 4 - 1
packages/canvas-engine/free-layout-core/src/service/workflow-drag-service.ts

@@ -664,7 +664,10 @@ export class WorkflowDragService {
         if (toNode && !this.isContainer(toNode)) {
           // 如果鼠标 hover 在 node 中的时候,默认连线到这个 node 的初始位置
           const portsData = toNode.getData(WorkflowNodePortsData)!;
-          toPort = portsData.inputPorts[0];
+          const { inputPorts } = portsData;
+          if (inputPorts.length === 1) {
+            toPort = inputPorts[0];
+          }
           const { hasError } = this.handleDragOnNode(toNode, fromPort, line, toPort, originLine);
           lineErrorReset = hasError;
         }