Close context menus on any click outside the menu (#895)

Current: Right click in e.g. `textarea` leaves litegraph context menus
open.
Proposed: Any click anywhere outside the context menu (or its sub-menus)
will close all context menus.
This commit is contained in:
filtered
2025-04-06 23:30:42 +10:00
committed by GitHub
parent ee625b4112
commit c77082fe2f

View File

@@ -18,6 +18,8 @@ export class ContextMenu<TValue = unknown> {
current_submenu?: ContextMenu<TValue>
lock?: boolean
controller: AbortController = new AbortController()
/**
* @todo Interface for values requires functionality change - currently accepts
* an array of strings, functions, objects, nulls, or undefined.
@@ -74,23 +76,26 @@ export class ContextMenu<TValue = unknown> {
// delay so the mouse up event is not caught by this element
}, 100)
// Close the context menu when a click occurs outside this context menu or its submenus
const { signal } = this.controller
const eventOptions = { capture: true, signal }
if (!this.parentMenu) {
document.addEventListener("pointerdown", (e) => {
if (e.target instanceof Node && !this.containsNode(e.target)) {
this.close()
}
}, eventOptions)
}
// this prevents the default context browser menu to open in case this menu was created when pressing right button
root.addEventListener(
"pointerup",
function (e) {
// console.log("pointerevents: ContextMenu up root prevent");
e.preventDefault()
return true
},
true,
)
root.addEventListener("pointerup", e => e.preventDefault(), true)
// Right button
root.addEventListener(
"contextmenu",
function (e) {
// right button
if (e.button != 2) return false
e.preventDefault()
return false
(e) => {
if (e.button === 2) e.preventDefault()
},
true,
)
@@ -98,11 +103,9 @@ export class ContextMenu<TValue = unknown> {
root.addEventListener(
"pointerdown",
(e) => {
// console.log("pointerevents: ContextMenu down");
if (e.button == 2) {
this.close()
e.preventDefault()
return true
}
},
true,
@@ -179,6 +182,19 @@ export class ContextMenu<TValue = unknown> {
}
}
/**
* Checks if {@link node} is inside this context menu or any of its submenus
* @param node The {@link Node} to check
* @param visited A set of visited menus to avoid circular references
* @returns `true` if {@link node} is inside this context menu or any of its submenus
*/
containsNode(node: Node, visited: Set<this> = new Set()): boolean {
if (visited.has(this)) return false
visited.add(this)
return this.current_submenu?.containsNode(node, visited) || this.root.contains(node)
}
addItem(
name: string | null,
value: string | IContextMenuValue<TValue> | null,
@@ -315,6 +331,7 @@ export class ContextMenu<TValue = unknown> {
}
close(e?: MouseEvent, ignore_parent_menu?: boolean): void {
this.controller.abort()
this.root.remove()
if (this.parentMenu && !ignore_parent_menu) {
this.parentMenu.lock = false
@@ -343,8 +360,6 @@ export class ContextMenu<TValue = unknown> {
const evt = document.createEvent("CustomEvent")
evt.initCustomEvent(event_name, true, true, params)
if (element.dispatchEvent) element.dispatchEvent(evt)
// @ts-expect-error
else if (element.__events) element.__events.dispatchEvent(evt)
// else nothing seems binded here so nothing to do
return evt
}