1 import {BaseSelection, LexicalEditor} from "lexical";
2 import {EditorUIManager} from "./manager";
4 import {el} from "../../utils/dom";
6 export type EditorUiStateUpdate = {
8 selection: BaseSelection|null;
11 export type EditorUiContext = {
12 editor: LexicalEditor; // Lexical editor instance
13 editorDOM: HTMLElement; // DOM element the editor is bound to
14 containerDOM: HTMLElement; // DOM element which contains all editor elements
15 scrollDOM: HTMLElement; // DOM element which is the main content scroll container
16 translate: (text: string) => string; // Translate function
17 error: (text: string|Error) => void; // Error reporting function
18 manager: EditorUIManager; // UI Manager instance for this editor
19 options: Record<string, any>; // General user options which may be used by sub elements
22 export interface EditorUiBuilderDefinition {
23 build: () => EditorUiElement;
26 export function isUiBuilderDefinition(object: any): object is EditorUiBuilderDefinition {
27 return 'build' in object;
30 export abstract class EditorUiElement {
31 protected dom: HTMLElement|null = null;
32 private context: EditorUiContext|null = null;
33 private abortController: AbortController = new AbortController();
35 protected abstract buildDOM(): HTMLElement;
37 setContext(context: EditorUiContext): void {
38 this.context = context;
41 getContext(): EditorUiContext {
42 if (this.context === null) {
43 throw new Error('Attempted to use EditorUIContext before it has been set');
49 getDOMElement(): HTMLElement {
51 this.dom = this.buildDOM();
57 rebuildDOM(): HTMLElement {
58 const newDOM = this.buildDOM();
59 this.dom?.replaceWith(newDOM);
65 return this.getContext().translate(text);
68 updateState(state: EditorUiStateUpdate): void {
72 emitEvent(name: string, data: object = {}): void {
74 this.dom.dispatchEvent(new CustomEvent('editor::' + name, {detail: data, bubbles: true}));
78 onEvent(name: string, callback: (data: object) => any, listenTarget: HTMLElement|null = null): void {
79 const target = listenTarget || this.dom;
81 target.addEventListener('editor::' + name, ((event: CustomEvent) => {
82 callback(event.detail);
83 }) as EventListener, { signal: this.abortController.signal });
88 if (this.dom && this.dom.isConnected) {
91 this.abortController.abort('teardown');
95 export class EditorContainerUiElement extends EditorUiElement {
96 protected children : EditorUiElement[] = [];
98 constructor(children: EditorUiElement[]) {
100 this.children.push(...children);
103 protected buildDOM(): HTMLElement {
104 return el('div', {}, this.getChildren().map(child => child.getDOMElement()));
107 getChildren(): EditorUiElement[] {
108 return this.children;
111 protected addChildren(...children: EditorUiElement[]): void {
112 this.children.push(...children);
115 protected removeChildren(...children: EditorUiElement[]): void {
116 for (const child of children) {
117 this.removeChild(child);
121 protected removeChild(child: EditorUiElement) {
122 const index = this.children.indexOf(child);
124 this.children.splice(index, 1);
128 updateState(state: EditorUiStateUpdate): void {
129 for (const child of this.children) {
130 child.updateState(state);
134 setContext(context: EditorUiContext) {
135 super.setContext(context);
136 for (const child of this.getChildren()) {
137 child.setContext(context);
142 for (const child of this.children) {
149 export class EditorSimpleClassContainer extends EditorContainerUiElement {
152 constructor(className: string, children: EditorUiElement[]) {
154 this.className = className;
157 protected buildDOM(): HTMLElement {
159 class: this.className,
160 }, this.getChildren().map(child => child.getDOMElement()));