import React from "react" import Core from "evite/src/core" import { DOMWindow } from "components/RenderWindow" import ContextMenu from "./components/contextMenu" import InternalContexts from "schemas/menu-contexts" export default class ContextMenuCore extends Core { static refName = "contextMenu" static namespace = "contextMenu" public = { show: this.show.bind(this), hide: this.hide.bind(this), registerContext: this.registerContext.bind(this), } contexts = Object() DOMWindow = new DOMWindow({ id: "contextMenu", className: "contextMenuWrapper", clickOutsideToClose: true, }) async onInitialize() { document.addEventListener("contextmenu", this.handleEvent.bind(this)) } registerContext(element, context) { this.contexts[element] = context } async generateItems(element) { let items = [] // find the closest context with attribute (context-menu) // if not found, use default context const parentElement = element.closest("[context-menu]") let contexts = [] if (parentElement) { contexts = parentElement.getAttribute("context-menu") ?? [] if (typeof contexts === "string") { contexts = contexts.split(",").map((context) => context.trim()) } } // if context includes ignore, return null if (contexts.includes("ignore")) { return null } // check if context includes no-default, if not, push default context and remove no-default if (contexts.includes("no-default")) { contexts = contexts.filter((context) => context !== "no-default") } else { contexts.push("default-context") } for await (const context of contexts) { let contextObject = this.contexts[context] || InternalContexts[context] if (typeof contextObject === "function") { contextObject = await contextObject(parentElement, element, { close: this.hide, }) } // push divider if (items.length > 0) { items.push({ type: "separator" }) } if (Array.isArray(contextObject)) { items.push(...contextObject) } else { items.push(contextObject) } } // fullfill each item with a correspondent index if missing declared items = items.map((item, index) => { if (!item.index) { item.index = index } return item }) // short items (if has declared index) items = items.sort((a, b) => a.index - b.index) // remove undefined items items = items.filter((item) => item !== undefined) return items } async handleEvent(event) { event.preventDefault() // get the cords of the mouse const x = event.clientX const y = event.clientY // get the component that was clicked const component = document.elementFromPoint(x, y) // check if is clicking inside a context menu or a children inside a context menu if (component.classList.contains("contextMenu") || component.closest(".contextMenu")) { return } const items = await this.generateItems(component) if (!items) { console.warn("No context menu items found, aborting") return false } this.show({ cords: { x, y, }, clickedComponent: component, items: items, ctx: { close: this.hide.bind(this), } }) } show(payload) { this.DOMWindow.render(React.createElement(ContextMenu, payload)) } hide() { this.DOMWindow.remove() } }