npm package: @knotx/plugins-drag
Description: Drag Plugin for Knotx
Author: boenfu
Latest version: 0.5.10

# @knotx/plugins-drag

拖拽插件，为 KnotX 提供节点拖拽功能。

## 安装

```bash
npm install @knotx/plugins-drag
```

## 概述

Drag 插件为 KnotX 提供了强大的节点拖拽功能，支持惯性拖拽、边界限制、边缘滚动等高级特性。该插件基于 `interactjs` 库实现，提供了灵活的拖拽配置和流畅的拖拽体验。

## 实现原理

Drag 插件的核心实现原理：

1. **InteractJS 集成**：使用 `interactjs` 库处理拖拽交互
2. **拖拽变换器**：支持动态修改拖拽配置的变换器系统
3. **边缘滚动**：拖拽到画布边缘时自动滚动画布
4. **惯性拖拽**：支持惯性拖拽效果，提供更自然的交互体验

## 依赖关系

### 核心依赖
- `@knotx/core`：提供基础插件架构
- `@knotx/decorators`：提供装饰器支持
- `interactjs`：提供拖拽交互功能
- `rxjs`：提供响应式编程支持

### 插件依赖
- `@knotx/plugins-canvas`：获取画布变换状态和边缘滚动配置（可选）

## API 文档

### 主要类

#### Drag

拖拽插件的主要类，继承自 `BasePlugin`。

```typescript
export class Drag extends BasePlugin {
  name = 'drag' as const
}
```

### 配置选项

#### DraggableOptions

```typescript
export type DraggableOptions = Omit<InteractDraggableOptions, 'listeners'>

interface DraggableOptions {
  /** 惯性配置 */
  inertia?: {
    enabled?: boolean
    resistance?: number
    minSpeed?: number
    endSpeed?: number
    smoothEndDuration?: number
  }
  /** 修饰器配置 */
  modifiers?: Modifier[]
  /** 其他 InteractJS 配置 */
  [key: string]: any
}
```

### 插件数据 (PluginData)

Drag 插件提供以下数据：

```typescript
interface PluginData {
  drag: {
    options: DraggableOptions
    registerOptionsTransformer: (transformer: DragOptionsTransformer) => () => void
  }
}
```

### 类型定义

#### DragOptionsTransformer

```typescript
export interface DragTransformerContext {
  nodeId: string
  node: Node
  defaultOptions: DraggableOptions
  currentOptions: DraggableOptions
}

export type DragOptionsTransformer = (context: DragTransformerContext) => DraggableOptions | undefined
```

## 使用示例

### 基本用法

```typescript
import { Drag } from '@knotx/plugins-drag'

const engine = new Engine({
  plugins: [Drag],
})
```

### 自定义拖拽配置

```typescript
const engine = new Engine({
  plugins: [Drag],
})

// 获取插件数据
const dragData = engine.getPluginData('drag')

// 修改默认配置
dragData.options = {
  inertia: {
    enabled: true,
    resistance: 30,
    minSpeed: 200,
    endSpeed: 10,
  },
  modifiers: [
    interact.modifiers.restrictRect({
      restriction: 'parent',
      endOnly: true,
    }),
  ],
}
```

### 使用拖拽变换器

```typescript
const engine = new Engine({
  plugins: [Drag],
})

const dragData = engine.getPluginData('drag')

// 注册拖拽变换器
const unregister = dragData.registerOptionsTransformer((context) => {
  const { nodeId, node, defaultOptions, currentOptions } = context

  // 根据节点类型或状态修改拖拽配置
  if (node.type === 'locked') {
    return {
      ...currentOptions,
      enabled: false, // 禁用拖拽
    }
  }

  if (node.type === 'slow') {
    return {
      ...currentOptions,
      inertia: {
        ...currentOptions.inertia,
        resistance: 100, // 增加阻力
      },
    }
  }

  return currentOptions
})

// 取消注册
unregister()
```

### 边界限制

```typescript
const engine = new Engine({
  plugins: [Drag],
})

const dragData = engine.getPluginData('drag')

// 限制拖拽范围
dragData.options = {
  modifiers: [
    interact.modifiers.restrictRect({
      restriction: '.canvas-container',
      endOnly: true,
    }),
  ],
}
```

### 网格对齐

```typescript
const engine = new Engine({
  plugins: [Drag],
})

const dragData = engine.getPluginData('drag')

// 网格对齐
dragData.options = {
  modifiers: [
    interact.modifiers.snap({
      targets: [
        interact.snappers.grid({ x: 20, y: 20 }),
      ],
      range: Infinity,
      relativePoints: [{ x: 0, y: 0 }],
    }),
  ],
}
```

### 与 Canvas 插件集成

```typescript
import { Canvas } from '@knotx/plugins-canvas'
import { Drag } from '@knotx/plugins-drag'

const engine = new Engine({
  plugins: [Canvas, Drag],
  pluginConfig: {
    canvas: {
      minScale: 0.1,
      maxScale: 3,
    },
  },
})

// 启用边缘滚动
const canvasData = engine.getPluginData('canvas')
canvasData.edgeScroll.setConfig({
  enabled: true,
  edgeSize: 100,
  maxSpeed: 10,
})
```

## 高级功能

### 动态拖拽配置

```typescript
class DynamicDragPlugin extends BasePlugin {
  @inject.drag.registerOptionsTransformer()
  registerOptionsTransformer!: (transformer: DragOptionsTransformer) => () => void

  @inject.selection.selected()
  selected!: SelectionSelectedItem[]

  @OnInit
  init() {
    this.registerOptionsTransformer((context) => {
      const { nodeId, currentOptions } = context

      // 多选拖拽时的特殊配置
      const isMultiSelected = this.selected.length > 1
        && this.selected.some(item => item.id === nodeId)

      if (isMultiSelected) {
        return {
          ...currentOptions,
          inertia: {
            ...currentOptions.inertia,
            enabled: false, // 多选时禁用惯性
          },
        }
      }

      return currentOptions
    })
  }
}
```

### 自定义拖拽修饰器

```typescript
const engine = new Engine({
  plugins: [Drag],
})

const dragData = engine.getPluginData('drag')

// 创建自定义修饰器
const customModifier = {
  set(coords: any) {
    // 自定义位置计算逻辑
    coords.x = Math.round(coords.x / 10) * 10 // 10px 网格对齐
    coords.y = Math.round(coords.y / 10) * 10
    return coords
  },
}

dragData.options = {
  modifiers: [customModifier],
}
```

### 拖拽状态监听

```typescript
class DragStatusPlugin extends BasePlugin {
  @inject.canvas.edgeScroll()
  edgeScroll!: PluginData['canvas']['edgeScroll']

  @subscribe.drag.isDragging()
  onDragStatusChange(isDragging: boolean) {
    if (isDragging) {
      console.log('开始拖拽')
      // 拖拽开始时启用边缘滚动
      this.edgeScroll.setConfig({ enabled: true })
    }
    else {
      console.log('结束拖拽')
      // 拖拽结束时禁用边缘滚动
      this.edgeScroll.setConfig({ enabled: false })
    }
  }
}
```

## 文件目录结构

```
packages/plugins-drag/
├── src/
│   ├── drag.tsx                # 主要实现文件
│   └── index.ts               # 导出文件
├── dist/                      # 构建输出目录
├── package.json              # 包配置文件
├── build.config.ts           # 构建配置
├── tsconfig.json             # TypeScript 配置
├── eslint.config.mjs         # ESLint 配置
└── CHANGELOG.md              # 更新日志
```

### 核心文件说明

- **drag.tsx**：包含 Drag 插件的主要实现，包括 InteractJS 集成、拖拽变换器系统和边缘滚动处理
- **index.ts**：导出 Drag 类和相关类型定义

## 最佳实践

### 性能优化

1. **合理使用惯性**：惯性效果虽然提供更好的用户体验，但在大量节点时可能影响性能
2. **限制拖拽范围**：使用 `restrictRect` 修饰器限制拖拽范围，避免节点拖拽到不合理的位置
3. **批量操作**：结合 `@knotx/plugins-batch` 插件优化批量拖拽操作

### 用户体验

1. **视觉反馈**：拖拽时提供清晰的视觉反馈，如阴影、透明度变化等
2. **网格对齐**：在需要精确定位的场景中启用网格对齐
3. **边缘滚动**：启用边缘滚动功能，改善大画布中的拖拽体验

## 注意事项

1. **事件冲突**：与其他交互插件（如选择）可能存在事件冲突，注意事件优先级
2. **内存管理**：及时清理不需要的拖拽变换器，避免内存泄漏
3. **性能考虑**：大量节点时，拖拽操作可能影响性能，考虑使用虚拟化
4. **移动端适配**：在移动端需要考虑触摸事件的处理

## 许可证

MIT