npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@knotx/core

v0.5.7

Published

Core for Knotx

Downloads

449

Readme

@knotx/core

English | 中文


📦 安装

npm install @knotx/core
yarn add @knotx/core
pnpm add @knotx/core

📖 概述

@knotx/core 是 Knotx 图形编辑器的核心包,提供了构建可视化图形编辑器所需的基础架构。它包含引擎管理、插件系统、交互管理、元素操作、层级渲染等核心功能,为开发者提供了强大而灵活的图形编辑能力。

🚀 快速开始

import { Engine } from '@knotx/core'

// 创建引擎实例
const engine = new Engine({
  container: { width: 800, height: 600, direction: 'horizontal' },
  nodes: [
    { id: '1', position: { x: 100, y: 100 }, data: { label: 'Node 1' } },
    { id: '2', position: { x: 300, y: 200 }, data: { label: 'Node 2' } }
  ],
  edges: [
    { id: 'e1', source: '1', target: '2', data: {} }
  ]
})

// 获取节点
const nodes = engine.getNodes()
console.log(nodes)

📚 完整 API 文档

🔧 核心类

Engine 类

引擎是 Knotx 的核心,负责管理整个图形编辑器的状态和行为。

// Engine 类的类型定义
interface Engine<TRenderType extends RenderType = RenderType, TNode extends IRecord = IRecord, TEdge extends IRecord = IRecord> {
  // 引擎的方法和属性
}

属性:

  • runtime: IEngineRuntime - 运行时配置
  • interaction: InteractionManager - 交互管理器
  • nodesManager: DataManager<Node<TNode>> - 节点数据管理器
  • edgesManager: DataManager<Edge<TEdge>> - 边数据管理器
  • container: Container - 容器配置
  • nodes: Node<TNode>[] - 节点数组
  • edges: Edge<TEdge>[] - 边数组
  • layers: Map<number, LayerComponent<TRenderType>[]> - 层级组件映射
  • plugins: IPlugin[] - 插件数组

方法:

  • getNodes(): Node<TNode>[] - 获取所有节点
  • getEdges(): Edge<TEdge>[] - 获取所有边
  • getNode({ id }): Node<TNode> | undefined - 根据 ID 获取节点
  • getEdge({ id }): Edge<TEdge> | undefined - 根据 ID 获取边
  • getNodeDraft({ id }): Node<TNode> | undefined - 获取节点草稿
  • getEdgeDraft({ id }): Edge<TEdge> | undefined - 获取边草稿
  • getPlugin<TPlugin>({ pluginName }): TPlugin | undefined - 获取插件
  • getNodeRenderer(type: string): NodeRenderType | undefined - 获取节点渲染器
  • getEdgeRenderer(type: string): EdgeRender | undefined - 获取边渲染器
  • getLayerComponents(layer: Layer): LayerComponent<TRenderType>[] - 获取层级组件
  • addNodePipe(pipe: DataOperationPipe<Node<TNode>>) - 添加节点操作管道
  • addEdgePipe(pipe: DataOperationPipe<Edge<TEdge>>) - 添加边操作管道
  • dispatchNodeOperation(operation: DataOperation<Node<TNode>>) - 派发节点操作
  • dispatchEdgeOperation(operation: DataOperation<Edge<TEdge>>) - 派发边操作
  • registerNodeRenderer(type: string, renderer: NodeRenderType): () => void - 注册节点渲染器
  • registerEdgeRenderer(type: string, renderer: EdgeRenderType, config: Required<EdgeConfig>): () => void - 注册边渲染器
  • registerPluginData<T, TP>(pluginName: T, property: TP, data: BehaviorSubject<PluginData[T][TP]>): void - 注册插件数据
  • subscribePluginData<T, TP>(pluginName: T, property: TP, observer: (value: PluginData[T][TP]) => void): Subscription | null - 订阅插件数据
  • registerPluginTool<T, TP>(pluginName: T, property: TP, data: { description: string; parameters: Schema; func: PluginTools[T][TP] }): void - 注册插件工具
  • resetPluginData<T>(pluginName: T): void - 重置插件数据
  • resetPluginTool<T>(pluginName: T): void - 重置插件工具
  • changePluginConfig({ pluginName, config }): void - 更改插件配置
  • callTool: CallToolMethod - 调用工具方法
  • listPlugins(): IPluginInfo[] - 列出所有插件
  • listPluginTools({ pluginName }): IToolInfo[] - 列出插件工具
  • listEngineTools(): IToolInfo[] - 列出引擎工具
  • destroy() - 销毁引擎

BasePlugin 类

插件基类,提供插件开发的基础功能。

// BasePlugin 抽象类的类型定义
abstract class BasePlugin<TPluginName extends string = string, TPluginConfig extends IRecord | undefined = undefined, TRenderType extends RenderType = RenderType> {
  // 插件的抽象方法和属性
}

属性:

  • abstract name: TPluginName - 插件名称
  • pluginId: string - 插件唯一ID
  • protected subscriptions: (SubscriptionLike | (() => void))[] - 订阅集合
  • protected callTool: Engine['callTool'] - 调用工具方法

方法:

  • onInit?(config: TPluginConfig): void - 初始化回调
  • onConfigChange?(config: TPluginConfig): void - 配置变更回调
  • onDestroy?(): void - 销毁回调

InteractionManager 类

交互管理器,负责管理用户交互的优先级和状态。

// InteractionManager 类的类型定义
class InteractionManager {
  // 交互管理器的方法和属性
}

属性:

  • active: Interaction | undefined - 当前活动交互

方法:

  • canInteract(priority: InteractionPriority): boolean - 检查是否可以交互
  • start(pluginId: string, type: string, priority: InteractionPriority): () => void - 开始交互
  • end(pluginId: string, type: string): void - 结束交互

Runtime 类

运行时管理器,处理上下文执行和值处理。

// Runtime 类的类型定义
class Runtime {
  // 运行时的方法和属性
}

属性:

  • currentContext: { type: any; value: any } | undefined - 当前上下文

方法:

  • runInContext<T>(type: any, handler: () => T, contextValue?: any): T - 在上下文中运行
  • getValue<T>(engine: Engine<any>, options: { paths: any[]; matcher?: (engine: Engine<any>) => BehaviorSubject<any>; selector?: (value: T, context?: any) => any }): any - 获取值
  • static getInstance(): Runtime - 获取单例实例
  • static DEFAULT_VALUE_HANDLER: <T>(value: BehaviorSubject<T> | T, options: { paths: string[]; selector?: (value: T, context?: any) => any; context?: any }) => T - 默认值处理器

DataManager 类

数据管理器,管理数据的增删改查和版本控制。

// DataManager 类的类型定义
class DataManager<TData extends IData = IData, TTag extends string = any> {
  // 数据管理器的方法和属性
}

属性:

  • readonly tag: TTag - 标签
  • readonly dataMap$: BehaviorSubject<Map<string, TData>> - 数据映射
  • readonly patchVersion$: BehaviorSubject<number> - 补丁版本
  • readonly mapVersion$: BehaviorSubject<number> - 映射版本
  • version: number - 版本号

方法:

  • init(initialDataList: TData[]): void - 初始化
  • getDataList$(): Observable<TData[]> - 获取数据列表流
  • getDataList(): TData[] - 获取数据列表
  • getData(id: string): TData | undefined - 获取数据
  • getOperations$(): Observable<DataOperation<TData>> - 获取操作流
  • dispatch(operation: DataOperation<TData>): void - 派发操作
  • getDraftDataList(draftId: string): TData[] | undefined - 获取草稿数据列表

DualRelation 类

双向关系管理器,管理父子节点关系。

// DualRelation 类的类型定义
class DualRelation<P, C> {
  // 双向关系管理器的方法和属性
}

属性:

  • version: BehaviorSubject<number> - 版本号

方法:

  • addRelation(parent: P, child: C): void - 添加关系
  • removeRelation(parent: P, child: C): void - 删除关系
  • getChildren(parent: P): C[] - 获取子节点
  • getParent(child: C): P | undefined - 获取父节点
  • clear(): void - 清空关系

Element 接口和 DOMElement 类

元素抽象,提供跨平台的元素操作接口。

interface Element {
  classList: ElementClassList
  attribute: ElementAttribute
  uniqSelector: string | undefined
  getRect: () => ElementRect
  addEventListener: EventListenerDefinition
  removeEventListener: EventListenerDefinition
  query: (selector: string) => Element | null
  queryAll: (selector: string) => Element[]
}

class DOMElement implements Element {
  constructor(readonly dom: HTMLElement) {}
  static fromDOM(dom: HTMLElement): DOMElement
}

🎭 类型定义

基础类型

type HorizontalAlignment = 'left' | 'right'
type VerticalAlignment = 'top' | 'bottom'
type Direction = 'horizontal' | 'vertical'
type IRecord = Record<string, any>
type Position = VerticalAlignment | HorizontalAlignment | `${HorizontalAlignment}-${VerticalAlignment}` | 'center'
type RenderType = (...args: any[]) => any

节点相关类型

interface NodePosition {
  x: number
  y: number
}

interface NodeMeasured {
  width: number
  height: number
}

interface Node<TData extends IRecord = IRecord> {
  id: string
  type?: string
  position: NodePosition
  measured?: NodeMeasured
  data: TData
}

interface NodeProps<TData extends IRecord = IRecord> {
  node: Node<TData>
  renderVersion?: number
}

type NodeRenderType<TD extends IRecord = any, TR = any> = (props: NodeProps<TD>) => TR
type NodeOperation<T extends IRecord = any> = DataOperation<Node<T>>
type NodeOperationPipe<T extends IRecord = any> = DataOperationPipe<Node<T>>
type NodeOperatorFunction<T extends IRecord = any, TArgs extends any[] = any[]> = (...args: TArgs) => NodeOperation<T>[]

边相关类型

interface EdgeConfig {
  sourcePosition?: Position
  targetPosition?: Position
  sourceYOffset?: string | number
  sourceXOffset?: string | number
  targetYOffset?: string | number
  targetXOffset?: string | number
}

interface Edge<TData extends IRecord = IRecord> extends EdgeConfig {
  id: string
  source: string
  target: string
  type?: string
  data?: TData
}

interface EdgeProps<TData extends IRecord = IRecord> {
  edge: Edge<TData>
  sourceX: number
  sourceY: number
  targetX: number
  targetY: number
  renderVersion?: number
}

type EdgeRenderType<TD extends IRecord = any, TR = any> = (props: EdgeProps<TD>) => TR

interface EdgeRender<TD extends IRecord = any, TR = any> {
  renderer: EdgeRenderType<TD, TR>
  config: Required<EdgeConfig>
}

type EdgeOperation<T extends IRecord = any> = DataOperation<Edge<T>>
type EdgeOperationPipe<T extends IRecord = any> = DataOperationPipe<Edge<T>>
type EdgeOperatorFunction<T extends IRecord = any, TArgs extends any[] = any[]> = (...args: TArgs) => EdgeOperation<T>[]

容器类型

interface Container {
  width: number
  height: number
  direction: Direction
}

层级类型

enum Layer {
  Canvas = 0,
  Background = 4,
  Edges = 16,
  Nodes = 64,
  Foreground = 256,
}

interface LayerComponent<TRenderType> {
  plugin: string
  name: string
  layer: Layer
  render: TRenderType
  offset?: number
}

插件类型

interface IPlugin<TPluginName extends string = string, TPluginConfig extends IRecord | undefined = any> {
  name: TPluginName
  pluginId: string
  description?: string
  onInit?: (config: TPluginConfig) => void
  onConfigChange?: (config: TPluginConfig) => void
  onDestroy?: () => void
}

type Plugin<TName extends string = string, TConfig extends IRecord | undefined = any, TRenderType extends RenderType = RenderType> = new (__: TConfig) => BasePlugin<TName, TConfig, TRenderType>

interface PluginData {} // 模块扩展接口
interface PluginTools {} // 模块扩展接口
interface EngineTools {} // 模块扩展接口

interface IPluginInfo {
  name: string
  description?: string
}

interface IToolInfo {
  type: 'function'
  name: string
  description: string
  parameters: Schema
}

数据操作类型

interface IData {
  id: string
}

interface DataAddOperation<T extends IData = IData> {
  type: 'add'
  data: T
}

interface DataRemoveOperation<_T extends IData = IData> {
  type: 'remove'
  id: string
}

interface DataUpdateOperation<T extends IData = IData> {
  type: 'update'
  id: string
  data: T
}

interface DataBatchOperation<T extends IData = IData> {
  type: 'batch'
  operations: DataOperation<T>[]
  isInit?: boolean
}

interface DataStartDraftOperation<T extends IData = IData> {
  type: 'startDraft'
  draftId: string
  data: T
}

interface DataCommitDraftOperation<T extends IData = IData> {
  type: 'commitDraft'
  draftId: string
  data: T
}

interface DataDiscardDraftOperation<T extends IData = IData> {
  type: 'discardDraft'
  draftId: string
}

interface DataDraftOperation<T extends IData = IData> {
  type: 'draft'
  draftId: string
  operation: DataOperation<T>
}

type DataOperation<T extends IData = IData> = DataAddOperation<T> | DataRemoveOperation<T> | DataUpdateOperation<T> | DataBatchOperation<T> | DataStartDraftOperation<T> | DataCommitDraftOperation<T> | DataDiscardDraftOperation<T> | DataDraftOperation<T>

interface DataOperationPipe<T extends IData = IData> {
  transform?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<DataOperation<T>, DataOperation<T>>
  preOperation?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<DataOperation<T>, DataOperation<T>>
  postOperation?: (operations$: Subject<DataOperation<T>>) => OperatorFunction<{ dataMap: Map<string, T>, operations: DataOperation<T>[] }, { dataMap: Map<string, T>, operations: DataOperation<T>[] }>
}

交互类型

enum InteractionPriority {
  InputActive = 2000,
  EntityConnectDrag = 1930,
  EntityMeasureDrag = 1920,
  EntityPositionDrag = 1910,
  ContinuousDrag = 1900,
  ContextMenu = 1800,
  KeyboardShortcut = 1700,
  ClickSelection = 1600,
  DoubleClickEdit = 1500,
  MarqueeSelection = 1300,
  LassoSelection = 1250,
  LongPress = 1100,
  HoverTooltip = 1000,
  CanvasPan = 900,
  CanvasZoom = 800,
  CanvasContextMenu = 700,
  CanvasClick = 600,
  MultiTouchGesture = 500,
}

interface Interaction {
  pluginId: string
  type: string
  priority: InteractionPriority
  active?: boolean
}

元素类型

interface ElementClassList {
  toString: () => string
  add: (...tokens: string[]) => void
  contains: (token: string) => boolean
  remove: (...tokens: string[]) => void
}

interface ElementAttribute {
  get: (name: string) => string | null
  set: (name: string, value: string) => void
  remove: (name: string) => void
}

interface ElementRect {
  height: number
  width: number
  x: number
  y: number
}

interface EventListenerDefinition<E = any> {
  (type: string, listener: ((evt: E) => void) | EventListenerObject<E>, options?: boolean | AddEventListenerOptions): void
}

引擎类型

interface EngineOptions<TNode extends IRecord = IRecord, TEdge extends IRecord = IRecord> {
  container: Container
  plugins?: Plugin[]
  pluginConfig?: IRecord
  nodes?: Node<TNode>[]
  edges?: Edge<TEdge>[]
  runtime?: IEngineRuntime
}

interface IEngineRuntime {
  render?: {
    getValue: <T, R = T>(value: BehaviorSubject<T> | T, options: {
      paths: string[]
      selector?: (value: T, context?: any) => R
      context?: any
    }) => T | R
  }
}

interface CallToolMethod {
  <T extends keyof PluginTools, TP extends keyof PluginTools[T]>(pluginName: Extract<T, keyof PluginTools>, toolName: Extract<TP, keyof PluginTools[T]>, ...args: Parameters<PluginTools[T][TP]>): ReturnType<PluginTools[T][TP]>
  <T extends keyof EngineTools>(engineToolName: Extract<T, keyof EngineTools>, ...args: Parameters<EngineTools[T]>): ReturnType<EngineTools[T]>
}

🛠️ 工具函数

钩子函数

function use<T>(hook: () => T): T
function use<T, TContext>(hook: () => { __contextRef__: T, __contextValue__: TContext }, context: TContext): T

层级函数

function getLayerRenders<TRenderType extends RenderType = RenderType>(plugin: IPlugin): LayerComponent<TRenderType>[]

工具函数(来自 @knotx/utils)

// BEM 样式类名工具
function bem(block: string, element?: string, modifier?: string, prefix?: string): string
function addBemModifier(className: string, modifier: string): string

// 唯一 ID 生成
function generateId(): string

// 符号工具
function getSymbol<TName extends string>(name: TName): symbol & { __knotx__?: TName }

// 数据操作工具
function isInitOperation<T extends IData>(operation: DataOperation<T>): operation is DataInitBatchOperation<T>
function isDraftOperation<T extends IData>(operation: DataOperation<T>): operation is Extract<DataOperation<T>, { draftId: string }>
function isEmptyBatchOperation<T extends IData>(operation: DataOperation<T>): operation is DataEmptyBatchOperation<T>
function flattenOperations<T extends IData>(operations: DataOperation<T>[]): DataOperation<T>[]
function emptyOperation<T extends IData>(): DataEmptyBatchOperation<T>
function buildDiffOperation<T extends IData>(previousDataMap: Map<string, T>, dataMap: Map<string, T>, compare?: (a: T | undefined, b: T | undefined) => boolean): DataBatchOperation<T>

📘 使用示例

基本使用

import { Engine } from '@knotx/core'

// 创建引擎
const engine = new Engine({
  container: { width: 800, height: 600, direction: 'horizontal' },
  nodes: [
    { id: '1', position: { x: 100, y: 100 }, data: { label: 'Node 1' } }
  ],
  edges: []
})

// 获取数据
const nodes = engine.getNodes()
const node = engine.getNode({ id: '1' })

插件开发

import { BasePlugin, Layer } from '@knotx/core'

class MyPlugin extends BasePlugin<'my-plugin', { theme: string }> {
  name = 'my-plugin' as const

  onInit(config: { theme: string }) {
    // 插件初始化逻辑
  }
}

数据操作

import { DataManager } from '@knotx/core'

const dataManager = new DataManager<Node>('nodes')
dataManager.init([
  { id: '1', position: { x: 0, y: 0 }, data: {} }
])

// 添加数据
dataManager.dispatch({
  type: 'add',
  data: { id: '2', position: { x: 100, y: 100 }, data: {} }
})

交互管理

import { InteractionManager, InteractionPriority } from '@knotx/core'

const interaction = new InteractionManager()
const cancel = interaction.start('plugin-id', 'drag', InteractionPriority.EntityPositionDrag)

// 检查是否可以交互
if (interaction.canInteract(InteractionPriority.ClickSelection)) {
  // 执行交互逻辑
}

// 取消交互
cancel()

🗂️ 文件目录结构

packages/core/
├── src/
│   ├── index.ts           # 主导出文件
│   ├── definition.ts      # 类型定义和接口
│   ├── engine.ts          # 核心引擎实现
│   ├── element.ts         # 元素抽象层
│   ├── plugin.ts          # 插件基类
│   ├── interaction.ts     # 交互管理器
│   ├── runtime.ts         # 运行时管理
│   ├── layer.tsx          # 层级渲染
│   ├── use.ts             # 钩子函数
│   ├── data.ts            # 数据管理(重新导出)
│   └── utils.ts           # 工具函数(重新导出)
├── dist/                  # 构建输出目录
├── package.json           # 包配置文件
├── tsconfig.json          # TypeScript 配置
├── build.config.ts        # 构建配置
└── README.md              # 项目文档

🔧 高级用法

自定义插件开发

import type { LayerComponent } from '@knotx/core'

import { BasePlugin, Layer } from '@knotx/core'

class CustomPlugin extends BasePlugin<'custom', { theme: string }> {
  name = 'custom' as const

  // 声明插件数据
  declare pluginData: {
    selectedNodes: string[]
    highlightColor: string
  }

  // 声明插件工具
  declare pluginTools: {
    selectNode: (nodeId: string) => void
    clearSelection: () => void
  }

  onInit(config: { theme: string }) {
    // 注册插件数据
    this.engine.registerPluginData('custom', 'selectedNodes', new BehaviorSubject([]))
    this.engine.registerPluginData('custom', 'highlightColor', new BehaviorSubject('#blue'))

    // 注册插件工具
    this.engine.registerPluginTool('custom', 'selectNode', {
      description: '选择节点',
      parameters: { type: 'string' },
      func: (nodeId: string) => {
        // 实现选择逻辑
        console.log('Selecting node:', nodeId)
      }
    })

    // 注册层级组件
    this.engine.registerLayerComponent({
      plugin: this.name,
      name: 'selection-layer',
      layer: Layer.Foreground,
      render: this.renderSelection.bind(this)
    })
  }

  renderSelection() {
    return '<div class="selection-overlay">Selection Layer</div>'
  }
}

数据操作管道

import type { EdgeOperation, NodeOperation } from '@knotx/core'

// 节点操作管道
engine.addNodePipe((operations: NodeOperation[]) => {
  return operations.map((op) => {
    if (op.type === 'update') {
      // 在更新前验证数据
      if (!validateNodeData(op.data)) {
        throw new Error('Invalid node data')
      }
    }
    return op
  })
})

// 边操作管道
engine.addEdgePipe((operations: EdgeOperation[]) => {
  return operations.filter((op) => {
    // 过滤无效的边操作
    return op.source !== op.target
  })
})

📄 开源协议

本项目采用 MIT License 开源协议。


🔗 相关链接


Made with ❤️ by the Knotx team