@mkljczk/lexical-remark
Version:
This package contains Markdown helpers and functionality for Lexical using remark-parse.
144 lines (125 loc) • 3.63 kB
text/typescript
/* eslint-disable @typescript-eslint/no-use-before-define */
import {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
ElementNode,
LexicalEditor,
LexicalNode,
NodeKey,
SerializedElementNode,
Spread,
} from 'lexical';
type SerializedCollapsibleContainerNode = Spread<
{
open: boolean;
},
SerializedElementNode
>;
export function convertDetailsElement(domNode: HTMLDetailsElement): DOMConversionOutput | null {
const openAttr = domNode.getAttribute('open');
const isOpen = openAttr !== null ? openAttr === 'true' : true;
const node = $createCollapsibleContainerNode(isOpen);
return {
node,
};
}
/**
* A Lexical node to represent an HTML details container
*/
export class CollapsibleContainerNode extends ElementNode {
__open: boolean;
constructor(open: boolean, key?: NodeKey) {
super(key);
this.__open = open;
}
static getType(): string {
return 'collapsible-container';
}
static clone(node: CollapsibleContainerNode): CollapsibleContainerNode {
return new CollapsibleContainerNode(node.__open, node.__key);
}
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;
}
updateDOM(prevNode: CollapsibleContainerNode, dom: HTMLDetailsElement): boolean {
if (prevNode.__open !== this.__open) {
dom.open = this.__open;
}
return false;
}
static importDOM(): DOMConversionMap<HTMLDetailsElement> | null {
return {
details: (domNode: HTMLDetailsElement) => {
return {
conversion: convertDetailsElement,
priority: 1,
};
},
};
}
static importJSON(serializedNode: SerializedCollapsibleContainerNode): CollapsibleContainerNode {
const node = $createCollapsibleContainerNode(serializedNode.open);
return node;
}
exportDOM(): DOMExportOutput {
const element = document.createElement('details');
element.setAttribute('open', this.__open.toString());
return { element };
}
exportJSON(): SerializedCollapsibleContainerNode {
return {
...super.exportJSON(),
open: this.__open,
type: 'collapsible-container',
version: 1,
};
}
/**
* Sets the open state of the details container
*/
setOpen(open: boolean): void {
const writable = this.getWritable();
writable.__open = open;
}
/**
* Gets the open state of the details container
*/
getOpen(): boolean {
return this.getLatest().__open;
}
/**
* Toggles the open state of the details container
*/
toggleOpen(): void {
this.setOpen(!this.getOpen());
}
}
/**
* Creates a Collapsible Container node with an initial open state
*
* @param isOpen The initial open state of the container
* @returns A Collapsible Container node
*/
export function $createCollapsibleContainerNode(isOpen: boolean): CollapsibleContainerNode {
return new CollapsibleContainerNode(isOpen);
}
/**
* A typeguard function to assert on a Collapsible Container node
*
* @param node A Lexical node
* @returns true if the node is a Collapsible Container node, otherwise false
*/
export function $isCollapsibleContainerNode(node: LexicalNode | null | undefined): node is CollapsibleContainerNode {
return node instanceof CollapsibleContainerNode;
}