prosemirror-viewのNodeViewで作成したDOMに対して、nodeにselectionがある時だけ表示を変更したい

前提
https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews を使って、https://github.com/ProseMirror/prosemirror-tables のtable nodeの外側にwrapperとしてdivを追加している
やりたいこと
nodeを選択している時だけ表示を変えたい

divに対してHTML classを付けたり外したりできれば良い
わからないこと
- nodeが選択された時・選択を外された時をどのように検知する?
- HTML classはどのように付与すれば良い?decorationsを介する?

NodeViewのプロパティとして selectNode()
deselectNode()
がある
けどこんな感じで定義してみて要素を選択したりしてみたけど特に発火しないなぁ…
selectNode() {
console.log("select");
}
deselectNode() {
console.log("deselect");
}

うーん、 view.state.selection
の様子を見ていると、テーブルセル内の文章を選択した場合はTextSelection
になり、テーブルセル自体を選択した場合は CellSelection
になるみたいだ
tableNodeの NodeSelection
にならないと発火しないのかもしれない

テーブルセル内の TextSelection
CellSelection
が作られた時、setSelection()
は発火するみたいだ

今ある情報
- constructorでProsemirrorNode, EditorView, getPos関数, decorationsが手に入る
- nodeに変更が起こるとupdate関数が呼ばれる
- EditorStateさえあればisInTable()を使ってテーブルを選択しているかは判別できる
- table nodeの子要素を選択したら
setSelection()
が呼ばれる- しかし選択が外れたことを検知することはできない

この辺りを見ながら allowTableNodeSelection: true
にしてみたらtableNodeが選択できるようになり、 selectNode
deselectNode
が発火するようになった
しかしやりたいことは table nodeの子nodeの選択の検知なんだよなぁ

同じような事例だ
I’d go with decorations, since node views aren’t notified of selection changes.
とあるのでdecorationsを介して行うのが良さそうだなぁ

innerDecorationsを介せば同じプラグインのdecorations情報を通知することができそう

こんな感じで一旦通知することはできた
// 今回のplugin
export const tablePlugin = () => {
return new Plugin({
key: new PluginKey("tablePlugin"),
props: {
nodeViews: {
table: (node, _, _, _, innerDecorations) => {
return new TableNodeView(node, innerDecorations);
},
},
decorations: (state) => {
const decorations: Decoration[] = [];
// selectionがtable node内にあるか判定する
if (isInTable(state)) {
const resolved = state.doc.resolve(state.selection.from);
// attrsは空で、specでtableを編集中ということを伝える
const decoration = Decoration.node(resolved.before(), resolved.after(), {}, { "tableEditing": true });
decorations.push(decoration);
}
return DecorationSet.create(state.doc, decorations);
},
},
});
};
// NodeViewに渡すclass
export class TableNodeView implements NodeView {
readonly node: ProsemirrorNode;
readonly dom: HTMLDivElement;
readonly contentDOM: HTMLElement;
readonly table: HTMLTableElement;
constructor(
node: ProsemirrorNode,
innerDecorations: DecorationSet,
) {
this.node = node;
this.dom = document.createElement("div");
this.dom.className = "tableWrapper";
this.table = this.dom.appendChild(document.createElement("table"));
this.contentDOM = this.table.appendChild(document.createElement("tbody"));
// specにtableEditingがあるdecorationがあるかを探す
const decorations = innerDecorations.find(undefined, undefined, (deco) => deco["tableEditing"] != null);
if (decorations.length !== 0) {
this.dom.classList.add("tableEditing");
}
}
update(node: ProsemirrorNode) {
return node.type.name === this.node.type.name;
}
}

しかしこのままだと以下の問題がある
- tableの中でTextSelectionなどを作ると, innerDecorationsには
DecorationGroup
インスタンスが渡される-
DecorationGroup
はDecorationSet
を複数持ち、それ自体にfind関数がないためエラーになってしまう -
DecorationGroup
はprivate APIのようでimportできない
-
- nodeViews propsの型情報にinnerDecorationが追加されていない

結論としては別のアプローチを取った方が良さそうなので一旦Close.
NodeViewで追加したDOMはProseMirrorのstateやmodelの管理下にないので扱いづらい。NodeViewでDOMを追加するのではなくDecoration.widgetなどで追加した方が都合が良さそう