Closed31

LexicalのCollapsibleを読む

ピン留めされたアイテム
hajimismhajimism

LexicalでToggle要素を実装する → LexicalのCollapsibleを読む

最初は自前実装するつもりだったけど結局ほぼコピペなのでタイトルを変えた

hajimismhajimism
hajimismhajimism

全体像としてはuseEffectのcleanupの中でmergeRegisterを書いているだけって感じ。

hajimismhajimism

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.

hajimismhajimism

その次は予期せぬDeleteに対応しているらしい?
たしかに挙動を確かめたら、コメントの通り「閉じてるときに隣の(弟?)NodeからDeleteが走ったら中身を消すのではなくてただ開くようにする」みたいな感じになってた。

hajimismhajimism

toggle blockが最上(下)部にあったときに上(下)キーを押すとParagraphNodeが挿入されるようになっている。

hajimismhajimism

space keyでtoggleしちゃったり、summaryでEnterおしてもcontentの方にカーソルが移動しなかったりと、結構面倒なんだな

hajimismhajimism

純粋なdetails/summaryは色々難しいことがわかったので、各Nodeの実装を見ていこう

hajimismhajimism

一番上は単純なutilぽい

export function convertSummaryElement(
  domNode: HTMLElement,
): DOMConversionOutput | null {
  const node = $createCollapsibleTitleNode();
  return {
    node,
  };
}
hajimismhajimism

Nodeは割と基本的な構成だが、下2つのメソッドが気になるな

hajimismhajimism

ちなみにDomそのものはこうらしい

  createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
    const dom = document.createElement('summary');
    dom.classList.add('Collapsible__title');
    return dom;
  }
hajimismhajimism

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());
  }
hajimismhajimism

DOM

  createDOM(config: EditorConfig): HTMLElement {
    const dom = document.createElement('div');
    dom.classList.add('Collapsible__content');
    return dom;
  }
hajimismhajimism

はじめましてのメソッドが生えてた

  isShadowRoot(): boolean {
    return true;
  }

implはここ
https://github.com/facebook/lexical/blob/main/packages/lexical/src/nodes/LexicalElementNode.ts#L573-L579

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か

hajimismhajimism

うーん、結局コピペになりそう
スタイルだけちょっと変えるか、くらい

hajimismhajimism

コピペしたけど変な挙動発見
titleが空のときにDelete押すとこうなる

hajimismhajimism

うーんわからん、本家のPlaygroundではtitleが空のときにDelete押すと全体が消える
contentの中身があるときはその中身だけ残る、完璧な挙動

hajimismhajimism

Inspectしてみると、detailsはあるけどsummaryが無い状態みたい

hajimismhajimism

結局わからん
一旦飽きたのでクローズする。のちのち解決策が判明したら追記する。

hajimismhajimism

完璧にぼくのミスだった。たぶんNodeの登録だけやってPluginを使うの忘れてた。改めてちゃんとしたらなおった。

このスクラップは2023/05/04にクローズされました