I have created custom ImageNode
by extending the DecoratorNode
.
When I click the ImageNode,
a click event is fired on the span
of that node as expected,
but still selection.getNodes()
doesn't have the image node.
while clicking on other elements works fine.
Here is the implementation of ImageNode
import type {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
LexicalEditor,
LexicalNode,
NodeKey,
SerializedEditor,
SerializedLexicalNode,
Spread,
} from "lexical";
import { createEditor, DecoratorNode } from "lexical";
import ImageComponent from "./ImageComponent";
export interface ImagePayload {
altText: string;
caption?: LexicalEditor;
height: number;
key?: NodeKey;
maxWidth?: number;
showCaption?: boolean;
src: string;
width: number;
captionsEnabled?: boolean;
}
function convertImageElement(domNode: Node): null | DOMConversionOutput {
if (domNode instanceof HTMLImageElement) {
const { alt: altText, src } = domNode;
const node = $createImageNode({
altText,
src,
width: 200,
height: 200,
});
return { node };
}
return null;
}
export type SerializedImageNode = Spread<
{
altText: string;
caption: SerializedEditor;
height: number;
maxWidth: number;
showCaption: boolean;
src: string;
width: number;
type: "image";
version: 1;
},
SerializedLexicalNode
>;
export class ImageNode
extends DecoratorNode<JSX.Element>
implements LexicalNode
{
__src: string;
__altText: string;
__width: number;
__height: number;
__maxWidth: number;
__showCaption: boolean;
__caption: LexicalEditor;
// Captions cannot yet be used within editor cells
__captionsEnabled: boolean;
static getType(): string {
return "image";
}
static clone(node: ImageNode): ImageNode {
return new ImageNode(
node.__src,
node.__altText,
node.__maxWidth,
node.__width,
node.__height,
node.__showCaption,
node.__caption,
node.__captionsEnabled,
node.__key
);
}
static importJSON(serializedNode: SerializedImageNode): ImageNode {
const { altText, height, width, maxWidth, caption, src, showCaption } =
serializedNode;
const node = $createImageNode({
altText,
height,
maxWidth,
showCaption,
src,
width,
});
const nestedEditor = node.__caption;
const editorState = nestedEditor.parseEditorState(caption.editorState);
if (!editorState.isEmpty()) {
nestedEditor.setEditorState(editorState);
}
return node;
}
exportDOM(): DOMExportOutput {
const element = document.createElement("img");
element.setAttribute("src", this.__src);
element.setAttribute("alt", this.__altText);
return { element };
}
static importDOM(): DOMConversionMap | null {
return {
img: (node: Node) => ({
conversion: convertImageElement,
priority: 0,
}),
};
}
constructor(
src: string,
altText: string,
maxWidth: number,
width: number,
height: number,
showCaption?: boolean,
caption?: LexicalEditor,
captionsEnabled?: boolean,
key?: NodeKey
) {
super(key);
this.__src = src;
this.__altText = altText;
this.__maxWidth = maxWidth;
this.__width = width ?? 300;
this.__height = height ?? 300;
this.__showCaption = showCaption || false;
this.__caption = caption || createEditor();
this.__captionsEnabled =
captionsEnabled || captionsEnabled === undefined;
}
exportJSON(): SerializedImageNode {
return {
altText: this.getAltText(),
caption: this.__caption.toJSON(),
height: this.__height,
maxWidth: this.__maxWidth,
showCaption: this.__showCaption,
src: this.getSrc(),
type: "image",
version: 1,
width: this.__width,
};
}
setWidthAndHeight(width: number, height: number): void {
const writable = this.getWritable();
writable.__width = width;
writable.__height = height;
}
setShowCaption(showCaption: boolean): void {
const writable = this.getWritable();
writable.__showCaption = showCaption;
}
// View
createDOM(config: EditorConfig): HTMLElement {
const span = document.createElement("span");
const theme = config.theme;
const className = theme.image;
if (className !== undefined) {
span.className = className;
}
return span;
}
updateDOM(): false {
return false;
}
getSrc(): string {
return this.__src;
}
getAltText(): string {
return this.__altText;
}
decorate(): JSX.Element {
return (
<ImageComponent
src={this.__src}
altText={this.__altText}
width={this.__width}
height={this.__height}
maxWidth={this.__maxWidth}
nodeKey={this.__key}
showCaption={this.__showCaption}
caption={this.__caption}
captionsEnabled={this.__captionsEnabled}
resizable={true}
/>
);
}
}
export function $createImageNode({
altText,
height,
maxWidth = 500,
captionsEnabled,
src,
width,
showCaption,
caption,
key,
}: ImagePayload): ImageNode {
return new ImageNode(
src,
altText,
maxWidth,
width,
height,
showCaption,
caption,
captionsEnabled,
key
);
}
export function $isImageNode(
node: LexicalNode | null | undefined
): node is ImageNode {
return node instanceof ImageNode;
}