LexicalのCollapsibleを読む
LexicalでToggle要素を実装する → LexicalのCollapsibleを読む
最初は自前実装するつもりだったけど結局ほぼコピペなのでタイトルを変えた
文脈
やりたいことは大体これなのでしっかり読んでいく
全体像としてはuseEffect
のcleanupの中でmergeRegisterを書いているだけって感じ。
mergeRegisterの中身を覗いてみると、まずTransformerでNodeの構造を管理しているところが見える。ご丁寧にコメント書いてある。
Structure enforcing transformers for each node type. In case nesting structure is not "Container > Title + Content" it'll unwrap nodes and convert it back to regular content.
その次は予期せぬDeleteに対応しているらしい?
たしかに挙動を確かめたら、コメントの通り「閉じてるときに隣の(弟?)NodeからDeleteが走ったら中身を消すのではなくてただ開くようにする」みたいな感じになってた。
toggle blockが最上(下)部にあったときに上(下)キーを押すとParagraphNodeが挿入されるようになっている。
試しに素のdetails/summaryタグを色々触ってみていて、summaryタグの三角と文字の間の空白をどうやって空けるのかわからなかったから実装を見たら、結構滅茶苦茶やってた。たぶん透明なボーダーで距離を稼いでいる。
space keyでtoggleしちゃったり、summaryでEnterおしてもcontentの方にカーソルが移動しなかったりと、結構面倒なんだな
手元のcontentEditableなsummaryでキャレットが出ないな...
純粋なdetails/summaryは色々難しいことがわかったので、各Nodeの実装を見ていこう
まずはtitle
全体像
一番上は単純なutilぽい
export function convertSummaryElement(
domNode: HTMLElement,
): DOMConversionOutput | null {
const node = $createCollapsibleTitleNode();
return {
node,
};
}
Nodeは割と基本的な構成だが、下2つのメソッドが気になるな
ちなみにDomそのものはこうらしい
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
const dom = document.createElement('summary');
dom.classList.add('Collapsible__title');
return dom;
}
insertNewAfter
- 閉じてるとき:detailsの次にpを挿入
- 開いているとき
- Elementがあればそちらにカーソルが移動
- なければpを挿入
API 仕様書、もうちょい各メソッドの説明入れてほしい。
順序おかしいけど次container
全体像
Nodeの型にopenの拡張があるのが印象的
DOMはここ
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
const dom = document.createElement('details');
dom.classList.add('Collapsible__container');
dom.open = this.__open;
dom.addEventListener('toggle', () => {
const open = editor.getEditorState().read(() => this.getOpen());
if (open !== dom.open) {
editor.update(() => this.toggleOpen());
}
});
return dom;
}
状態管理のためのメソッドが生えてる
setOpen(open: boolean): void {
const writable = this.getWritable();
writable.__open = open;
}
getOpen(): boolean {
return this.getLatest().__open;
}
toggleOpen(): void {
this.setOpen(!this.getOpen());
}
それ以上はなさそう。
次content
DOM
createDOM(config: EditorConfig): HTMLElement {
const dom = document.createElement('div');
dom.classList.add('Collapsible__content');
return dom;
}
はじめましてのメソッドが生えてた
isShadowRoot(): boolean {
return true;
}
implはここ
A shadow root is a Node that behaves like RootNode. The shadow root (and RootNode) mark the end of the hiercharchy, most implementations should treat it as there's nothing (upwards) beyond this point. For example, node.getTopLevelElement(), when performed inside a TableCellNode will return the immediate first child underneath TableCellNode instead of RootNode.
たしかにtoggle内のcontentはshadowRootか
うーん、結局コピペになりそう
スタイルだけちょっと変えるか、くらい
コピペしたけど変な挙動発見
titleが空のときにDelete押すとこうなる
うーんわからん、本家のPlaygroundではtitleが空のときにDelete押すと全体が消える
contentの中身があるときはその中身だけ残る、完璧な挙動
Inspectしてみると、detailsはあるけどsummaryが無い状態みたい
それはこいつが機能していたら起きないはずなのだが
結局わからん
一旦飽きたのでクローズする。のちのち解決策が判明したら追記する。
完璧にぼくのミスだった。たぶんNodeの登録だけやってPluginを使うの忘れてた。改めてちゃんとしたらなおった。