3 DOMConversionMap, DOMConversionOutput,
7 SerializedElementNode, Spread,
8 EditorConfig, DOMExportOutput,
11 import {extractDirectionFromElement} from "lexical/nodes/common";
13 export type SerializedDetailsNode = Spread<{
16 }, SerializedElementNode>
18 export class DetailsNode extends ElementNode {
20 __summary: string = '';
21 __open: boolean = false;
28 const self = this.getWritable();
33 const self = this.getLatest();
37 setSummary(summary: string) {
38 const self = this.getWritable();
39 self.__summary = summary;
42 getSummary(): string {
43 const self = this.getLatest();
44 return self.__summary;
47 setOpen(open: boolean) {
48 const self = this.getWritable();
53 const self = this.getLatest();
57 static clone(node: DetailsNode): DetailsNode {
58 const newNode = new DetailsNode(node.__key);
59 newNode.__id = node.__id;
60 newNode.__dir = node.__dir;
61 newNode.__summary = node.__summary;
62 newNode.__open = node.__open;
66 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
67 const el = document.createElement('details');
69 el.setAttribute('id', this.__id);
73 el.setAttribute('dir', this.__dir);
77 el.setAttribute('open', 'true');
80 const summary = document.createElement('summary');
81 summary.textContent = this.__summary;
82 summary.setAttribute('contenteditable', 'false');
83 summary.addEventListener('click', event => {
84 event.preventDefault();
85 _editor.update(() => {
95 updateDOM(prevNode: DetailsNode, dom: HTMLElement) {
97 if (prevNode.__open !== this.__open) {
98 dom.toggleAttribute('open', this.__open);
101 return prevNode.__id !== this.__id
102 || prevNode.__dir !== this.__dir
103 || prevNode.__summary !== this.__summary;
106 static importDOM(): DOMConversionMap|null {
108 details(node: HTMLElement): DOMConversion|null {
110 conversion: (element: HTMLElement): DOMConversionOutput|null => {
111 const node = new DetailsNode();
113 node.setId(element.id);
117 node.setDirection(extractDirectionFromElement(element));
120 const summaryElem = Array.from(element.children).find(e => e.nodeName === 'SUMMARY');
121 node.setSummary(summaryElem?.textContent || '');
128 summary(node: HTMLElement): DOMConversion|null {
130 conversion: (element: HTMLElement): DOMConversionOutput|null => {
131 return {node: 'ignore'};
139 exportDOM(editor: LexicalEditor): DOMExportOutput {
140 const element = this.createDOM(editor._config, editor);
141 const editable = element.querySelectorAll('[contenteditable]');
142 for (const elem of editable) {
143 elem.removeAttribute('contenteditable');
146 element.removeAttribute('open');
151 exportJSON(): SerializedDetailsNode {
153 ...super.exportJSON(),
157 summary: this.__summary,
161 static importJSON(serializedNode: SerializedDetailsNode): DetailsNode {
162 const node = $createDetailsNode();
163 node.setId(serializedNode.id);
164 node.setDirection(serializedNode.direction);
170 export function $createDetailsNode() {
171 return new DetailsNode();
174 export function $isDetailsNode(node: LexicalNode | null | undefined): node is DetailsNode {
175 return node instanceof DetailsNode;