headless-vpl
v0.3.0
Published
A headless library for building visual programming languages
Maintainers
Readme
特徴
🧩 Headless — UI を持たない。React、Vue、Svelte、バニラ DOM、何でも使える。
🎯 VPL 特化 — 汎用キャンバスライブラリではない。VPL のために設計された API。
📦 Pure TypeScript — ゼロ依存。完全な型安全。バニラ TS で書かれている。
🔬 Simple > Easy — ブラックボックスを作らない。全てを理解し制御できる。
Blockly・ReactFlow との比較
| | Headless VPL | Blockly | ReactFlow | | -------------- | -------------------------------- | ---------------------- | ------------------------ | | VPL タイプ | ブロック + フロー + ハイブリッド | ブロックのみ | フローのみ | | フレームワーク | 何でも(バニラ TS) | なし(独自レンダラー) | React のみ | | レンダリング | 自由(DOM オーバーレイ) | Blockly レンダラー固定 | React コンポーネント固定 | | 型安全 | TypeScript ジェネリクス完全対応 | JSON/文字列ベース | 部分的 | | デザイン自由度 | 完全 | 制限あり | コンポーネントレベル | | API スタイル | 薄くコンポーザブル | 大きく独自的 | イベント駆動・暗黙的 |
コード削減実績
| 機能 | 従来 | Headless VPL | 削減率 | | ----------------- | ----- | ------------ | -------- | | レイアウト構築 | 71 行 | 22 行 | -69% | | ドラッグ&ドロップ | 38 行 | 8 行 | -78% | | スタイリング | 7 行 | 1 行 | -85% |
Quick Start
npm install headless-vpl最小構成 — ドラッグ可能な 2 ノードをエッジで接続:
import {
Workspace,
Container,
Connector,
Edge,
Position,
SvgRenderer,
InteractionManager,
DomSyncHelper,
bindWheelZoom,
bindDefaultShortcuts,
} from 'headless-vpl'
import { getMouseState } from 'headless-vpl/util/mouse'
import { animate } from 'headless-vpl/util/animate'
// 1. ワークスペース + レンダラー
const workspace = new Workspace()
const svg = document.querySelector('#workspace') as SVGSVGElement
new SvgRenderer(svg, workspace)
// 2. ノード作成
const nodeA = new Container({
workspace,
position: new Position(100, 50),
name: 'nodeA',
width: 160,
height: 60,
children: {
output: new Connector({ position: new Position(160, -30), name: 'out', type: 'output' }),
},
})
const nodeB = new Container({
workspace,
position: new Position(400, 50),
name: 'nodeB',
width: 160,
height: 60,
children: {
input: new Connector({ position: new Position(0, -30), name: 'in', type: 'input' }),
},
})
// 3. エッジで接続
new Edge({ start: nodeA.children.output, end: nodeB.children.input, edgeType: 'bezier' })
// 4. インタラクション(DnD + パン + マーキー選択 + リサイズ)
const canvasEl = svg.parentElement as HTMLElement
const containers = [nodeA, nodeB]
const interaction = new InteractionManager({
workspace,
canvasElement: canvasEl,
containers: () => containers,
})
const mouse = getMouseState(canvasEl, {
mousedown: (_bs, mp, ev) => interaction.handlePointerDown(mp, ev),
mouseup: (_bs, mp) => interaction.handlePointerUp(mp),
})
// 5. ホイールズーム + キーボードショートカット
bindWheelZoom(canvasEl, { workspace })
bindDefaultShortcuts({ workspace, element: document.body, containers: () => containers })
// 6. アニメーションループ
animate(() => {
interaction.tick(mouse.mousePosition, mouse.buttonState)
})コアコンセプト: 4 つの型
全ての VPL は 4 つの普遍的パターンに分解できる:
| パターン | 内容 | 対応 API |
| -------- | ----------------------------------------- | ----------------------------------------------------- |
| 描画 | レスポンシブサイジング、オートレイアウト | Container, AutoLayout, SvgRenderer |
| 接続 | コネクター、エッジ、親子関係 | Connector, Edge, SnapConnection |
| 移動 | ドラッグ&ドロップ、スナップ、グループ移動 | DragAndDrop, SnapConnection, InteractionManager |
| 入力 | テキスト、数字、トグル、スライダー | 開発者の DOM(フレームワーク非依存) |
単方向データフロー
フロントエンド(React / Vue / vanilla)
→ マウス/キーボードイベント
→ Headless VPL コア(Workspace, Container, Edge)
→ EventBus 通知
→ SvgRenderer(デバッグ用ワイヤーフレーム)
→ 開発者の DOM コンポーネント(本番 UI)Headless アーキテクチャ
SVG ワイヤーフレームでヒット検知とデバッグを行い、実際の UI は DOM を上に被せるだけ:
┌─ Canvas ─────────────────────────┐
│ SVG layer (wireframe, invisible)│ ← ヒット検知、デバッグ
│ DOM overlay (your components) │ ← ユーザーに見える UI
└──────────────────────────────────┘DomSyncHelper で Headless 座標系と DOM 位置を自動同期。
主要 API
全 API の詳細は docs/api-reference.md を参照。
コア
| API | 説明 |
| ---------------------------------------------------- | -------------------------------------------------------- |
| Workspace | ルートコンテナ。ビューポート・選択・履歴・イベントを管理 |
| Container<T> | 主要構成要素。コネクターや AutoLayout を型付きで保持 |
| Connector | 入出力接続ポイント |
| Edge | コネクター間の接続線。4 種のパスアルゴリズム |
| AutoLayout | CSS Flexbox ライクな自動レイアウト |
// ノードの定義
const node = new Container({
workspace,
position: new Position(100, 50),
name: 'myNode',
width: 200,
height: 80,
widthMode: 'hug', // コンテンツにフィット
children: {
input: new Connector({ position: new Position(0, -40), name: 'in', type: 'input' }),
output: new Connector({ position: new Position(200, -40), name: 'out', type: 'output' }),
},
})
node.children.input // 型安全にアクセスイベント
| API | 説明 |
| -------------------------------------------------------------- | ------------------------------------------- |
| EventBus | パブリッシュ/サブスクライブイベントシステム |
| SelectionManager | 選択状態管理 |
const unsub = workspace.on('move', (event) => console.log(event.target))
workspace.selection.select(node)
workspace.selection.deselectAll()履歴
| API | 説明 |
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| History | Undo/Redo スタック |
| MoveCommand | 移動記録 |
| AddCommand / RemoveCommand | 追加/削除記録 |
| ConnectCommand | 接続記録 |
| NestCommand | ネスト記録 |
| BatchCommand | 複数コマンドをまとめる |
workspace.history.execute(new MoveCommand(element, 0, 0, 100, 100))
workspace.history.undo()
workspace.history.redo()レンダリング
| API | 説明 |
| ---------------------------------------------------- | ----------------------------------------- |
| SvgRenderer | デバッグ用 SVG ワイヤーフレームレンダラー |
new SvgRenderer(svgElement, workspace)ユーティリティ
| API | 説明 |
| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| InteractionManager | DnD・パン・マーキー・リサイズ・Edge 作成を統合管理 |
| DragAndDrop | ドラッグ&ドロップ |
| SnapConnection | スナップ接続管理 |
| EdgeBuilder | ドラッグで Edge を作成 |
| NestingZone | AutoLayout へのネスト管理 |
| DomSyncHelper | Container → DOM 位置同期 |
| bindWheelZoom | ホイールズーム |
| bindDefaultShortcuts | 標準キーボードショートカット |
| observeContentSize | DOM サイズ → Container 自動同期 |
| KeyboardManager | カスタムキーバインド |
| computeAutoPan | 端ドラッグ時の自動パン |
| detectResizeHandle | リサイズハンドル検出・適用 |
| createMarqueeRect | マーキー範囲選択 |
| snapToGrid | グリッドスナップ |
| copyElements / pasteElements | コピー/ペースト |
| screenToWorld / worldToScreen | 座標変換 |
// InteractionManager ですべてを統合
const interaction = new InteractionManager({
workspace,
canvasElement: canvasEl,
containers: () => containers,
connectors: () => connectors,
snapConnections: [snapConn],
nestingZones: zones,
edgeBuilder,
})型一覧
全量は docs/api-reference.md を参照。
| 型 | 説明 |
| --------------------- | -------------------------------------------------------------------------------- |
| IWorkspaceElement | ワークスペース要素のインターフェース |
| IEdge | エッジのインターフェース |
| IPosition | { x: number, y: number } |
| Viewport | { x: number, y: number, scale: number } |
| VplEvent | { type, target, data? } |
| VplEventType | 'move' \| 'connect' \| 'disconnect' \| ... |
| SizingMode | 'fixed' \| 'hug' \| 'fill' |
| EdgeType | 'straight' \| 'bezier' \| 'step' \| 'smoothstep' |
| Command | { execute(): void, undo(): void } |
| InteractionMode | 'idle' \| 'panning' \| 'dragging' \| 'marquee' \| 'resizing' \| 'edgeBuilding' |
| ConnectionValidator | () => boolean |
| SnapStrategy | (source, target, dragContainers) => boolean |
レシピ
各種 VPL のコード例は docs/recipes.md を参照。
- ブロック型 VPL(Scratch スタイル)
- フロー型 VPL(ReactFlow スタイル)
- ハイブリッド型 VPL(ブロック + フロー)
- ネスティング(スロット/入れ子)
- InteractionManager 統合セットアップ
Claude Code スキル
Claude Code を使った開発を支援するスキル(スラッシュコマンド)を同梱しています。
| コマンド | 説明 | 使用例 |
| --------------------- | ----------------------------------------------------- | ------------------------------------------ |
| /create-vpl | 要件に基づいて headless-vpl を使った VPL コードを生成 | /create-vpl Scratchのようなブロック型VPL |
| /headless-vpl-guide | ライブラリの API・アーキテクチャに関する質問に回答 | /headless-vpl-guide DnDの設定方法 |
| /debug-vpl | 典型的な問題の診断と解決策を提示 | /debug-vpl ドラッグが動かない |
ドキュメント
詳細なドキュメントは docs-site を参照してください。
開発
npm install && npm run dev # 開発サーバー起動
npm run build # プロダクションビルド
npm run build:lib # ライブラリビルド
npm run test # テスト実行
npm run lint # リント
npm run typecheck # 型チェック
npm run docs:dev # ドキュメントサイト起動コントリビューション
コントリビューションを歓迎しています!詳細は CONTRIBUTING.md を参照してください。
ライセンス
MIT
