コンテンツにスキップ

エディタキット API

すべてパッケージルートからエクスポートされています。粒度の細かい インポート用にモジュール別エントリ(/commands/selection/serializer/dnd/overlay)もあります。

各ファクトリはコマンドオブジェクトを返し、CommandStack が適用する まで何も起きません。挿入インデックスは parent.childNodes 上の 位置で、移動/挿入対象のノードを除いて数えます — そのため moveNode は同一親内でも親をまたいでも同じ意味論で動きます。

ファクトリ動作
setAttribute(node, name, value)属性を設定(value: null で削除)
removeAttribute(node, name)setAttribute(node, name, null) の別名
setText(node, text)textContent を置換
insertNode(parent, node, at)挿入点に挿入
removeNode(node)削除。undo で元の位置に復元
moveNode(node, parent, at)削除 + 挿入。undo で元の位置に復元

挿入点は childNodes インデックスまたは { before: Element | null }(null で末尾追加)です — 要素ベースの UI コード (Alt+矢印での並べ替え、「コンテナに追加」)が空白テキストノードを 数える必要はもうありません。数値形式が必要な場面では indexBefore(parent, ref, exclude?) が「この要素の前」を本キットの 全 API が使うインデックスへ変換します(ドラッグコントローラが ドロップ報告に使うのと同じヘルパです)。

const stack = new CommandStack();
stack.apply(command); // 実行 + 記録
stack.apply(command, { coalesce: true }); // 同一対象への連続編集をマージ
stack.transact(() => { …複数の apply() 呼び出し… }); // undo 1 単位に
stack.undo(); stack.redo(); // → boolean(空なら false)
stack.canUndo; stack.canRedo; // ゲッター
stack.clear();
  • Coalescing は同じノード + 属性への連続する setAttribute/setText をマージします — インスペクタでの タイピングが undo 1 ステップになります。
  • transact() は任意のコマンド群(挿入 + その初期属性など)を グループ化し、逆順に revert します。入れ子は不可。
  • すべての変更後に change CustomEvent を発火します (detail.action: apply | undo | redo | clear)— UI の 再描画と Selection.prune() をここに配線してください。

primary(最初に選択されたノード)を持つ、順序付きの要素集合です。 実際に選択が変わったときだけ change を発火します。

const sel = new Selection();
sel.select(node); // 置き換え
sel.select(node, { additive: true }); // 追加(shift クリック)
sel.toggle(node); sel.deselect(node); sel.clear();
sel.items; sel.primary; sel.size; sel.isSelected(node);
sel.prune(); // 切断されたノードを除去(undo/redo 後)

pickBlock(target, { root, manifest }) は、クリックターゲットを キャンバスが選択すべきものへ解決します: マニフェストのブロックを クラスに持つ最も近い祖先、なければ要素自身 — root 自体や外側は 決して返しません(null = 選択解除)。instanceof を使わないため document をまたいでも安全で、iframe キャンバスでも動きます:

canvas.addEventListener('click', (e) => {
const node = pickBlock(e.target, { root: canvas, manifest });
node ? editor.selection.select(node) : editor.selection.clear();
});

予約済みの足場名前空間 — data-hc-editor- 接頭辞の属性と data-hc-editor-only 付き要素 — は両方のシリアライザが 剥がします。エディタのクロームが生成物に漏れることはありません。

serialize(root) // → 生成物 HTML(root の子、クリーン済み)
toJson(el, { manifest }) // → JSON 射影(マニフェストで component 注釈)
fromJson(json, doc?) // → DOM ノード

JSON 射影は、文書化された正規化(空白のみのテキストノードと コメントの除去、属性順のソート)を除いて双方向です。component フィールドは派生メタデータ(マニフェストのブロックに一致する最初の クラス)で、デコード時には無視されます。

{
"tag": "button",
"component": "hc-button",
"attrs": { "class": "hc-button", "data-variant": "primary" },
"children": [{ "text": "Save" }]
}

createDragController は pointer-events ベースのエンジンです (HTML5 DnD ではありません)。ドロップ可能領域は data-hc-editor-container でマークします — 足場属性なので serialize 時に剥がれます。

const dnd = createDragController({
root: canvas, // 必須
frame: canvasIframe, // キャンバスを収める iframe(下記参照)
canAccept(container, payload) {}, // 拒否 → 上位のコンテナを探索
onPreview(target) {}, // { container, index } | null
onDrop({ container, index, payload }) {},
onCancel() {},
threshold: 4, // move がドラッグになるまでの px
hitTest, rectOf, // ジオメトリ注入(テスト、iframe)
});
dnd.startInsert(data, pointerEvent); // パレットから(即アクティブ)
dnd.startMove(node, pointerEvent); // キャンバス上のノード(しきい値あり)
dnd.dragging; // boolean
dnd.dispose();
  • 報告される indexドラッグ中のノードを除いた childNodes 位置です — insertNode/moveNode にそのまま渡せるので、すべての ドロップが undo 可能なまま保たれます。
  • payload{ type: 'insert', data, node: null } または { type: 'move', node }
  • Escape でキャンセル。しきい値未満のクリックはドラッグに ならないので、通常のクリックは選択に届きます。
  • document をまたぐドラッグ: キャンバスが iframe 内にある場合、 パレットのドラッグはホスト document で始まり、pointer イベントは フレーム境界を越えません。iframe を frame に渡すと、コントローラは 両方の document をリッスンし、ホストビューポート座標をフレームの rect を通じてキャンバス座標へ変換します — 親 document のパレットからの startInsert がそのまま完了します。

選択枠とドロップインジケータを、キャンバスののマウント要素に 描画します — 生成物の DOM はクリーンなまま。キャンバスが iframe 内に ある場合は frame を渡してください。

const overlay = new Overlay({ mount, frame? , rectOf? });
overlay.showSelection(nodes); // Selection の 'change' に配線
overlay.showDropIndicator(t); // onPreview の出力をそのまま受ける(null で非表示)
overlay.refresh(); // scroll/resize/undo/redo で再計算
overlay.dispose();

外観のフック: .hc-editor-overlay__selection.hc-editor-overlay__indicator(+ data-orientationdata-empty)、 フォールバックとして --hc-editor-selection-color / --hc-editor-indicator-color。インラインで設定されるのはジオメトリ だけです。

const editor = createEditor({ root: canvasBody, manifest });
const overlay = new Overlay({ mount: hostLayer, frame: canvasIframe });
editor.selection.addEventListener('change', (e) => overlay.showSelection(e.detail.items));
editor.stack.addEventListener('change', () => overlay.refresh());
const dnd = createDragController({
root: canvasBody,
onPreview: (t) => overlay.showDropIndicator(t),
onDrop: ({ container, index, payload }) => {
editor.stack.apply(
payload.type === 'move'
? moveNode(payload.node, container, index)
: insertNode(container, instantiate(payload.data), index),
);
},
});