Files
ausboss-ComfyUI-AusBoss/js/shared/canvas_passthrough.mjs
ausboss 6c705b2e42 Panels: hand an Assets card dropped on them to the node
In the classic renderer a picture's card dragged from ComfyUI's Assets
panel loaded into Image Crop + Rotate + Pad only when dropped on the
node's title bar. Dropped on the picture or the controls, the drag never
reached the canvas, which is where ComfyUI finds the node under a drop,
so nothing happened.

The pack already hands a file dropped on one of its panels to the node
underneath. An Assets card carries no file, only
application/x-comfy-asset-info, so that drag now takes the same path.
Nodes 2.0 already handed it to the node.
2026-10-21 15:04:55 -05:00

126 lines
6.1 KiB
JavaScript

// Lets the graph keep its mouse gestures while the pointer is over one of the
// pack's panels, and lets ComfyUI keep its keyboard shortcuts while the cursor
// is in one of the pack's fields.
//
// A panel or card that takes the mouse (a picture stage, a number box, a
// button) also takes the mouse wheel, so the graph stopped zooming the moment
// the pointer crossed it. ComfyUI forwards those gestures for its own text
// boxes only. These helpers decide what to hand back to the graph; the entry
// in js/canvas_passthrough/ wires them to the page.
// The wheel belongs to the panel while it can still scroll that way (a long
// text box, a list of rows), or while a text field has the keyboard.
export function canScrollFurther(element, deltaY) {
if (!element || !deltaY) return false;
const room = element.scrollHeight - element.clientHeight;
if (room <= 1) return false;
return deltaY > 0 ? element.scrollTop + element.clientHeight < element.scrollHeight - 1 : element.scrollTop > 0;
}
// Does the wheel belong to something inside the panel, between the target
// and the panel's own root? `styleOf` is getComputedStyle, passed in so this
// stays testable without a page. `host` is the frontend's wrapper around the
// panel: a pointer on the wrapper itself is on nothing inside the panel.
export function panelKeepsWheel(target, event, { root, host, styleOf, activeElement }) {
for (let element = target; element && element !== root && element !== host; element = element.parentElement) {
const tag = String(element.tagName ?? "").toUpperCase();
if ((tag === "TEXTAREA" || tag === "INPUT") && element === activeElement) return true;
const overflow = styleOf(element)?.overflowY;
if ((overflow === "auto" || overflow === "scroll") && canScrollFurther(element, event.deltaY)) return true;
}
return false;
}
// Gestures that start over a panel and still mean "move the graph": the
// middle button (pan), and Ctrl + Shift + left button (drag-zoom) when the
// user has that shortcut switched on.
export function graphDragStarts(event, { dragZoomEnabled }) {
if (event.button === 1 || event.buttons === 4) return true;
return Boolean(dragZoomEnabled && event.ctrlKey && event.shiftKey && !event.altKey && event.buttons);
}
const WRAPPERS = ".dom-widget, .lg-node-widgets";
const isOurs = (element) => String(element?.className ?? "").includes("ausboss-");
// The frontend's wrapper around the widget a pointer is on: every classic DOM
// widget sits in a .dom-widget, and in Nodes 2.0 (Vue nodes) a panel sits
// inside the node's .lg-node-widgets.
export function panelHost(target) {
return target?.closest?.(WRAPPERS) ?? null;
}
// Where one of the pack's panels sits. The pack's panels all carry an
// "ausboss-" class on their root. Returns that root (the outermost "ausboss-"
// element under the wrapper), or null for anything that is not one of ours
// (core's own text boxes forward the wheel themselves).
//
// Most panels let the mouse through wherever they have nothing to click: a
// preview picture, the text of a note, the padding of a card. In the classic
// renderer the pointer then lands on the .dom-widget wrapper itself, which
// takes the mouse and hands nothing on. That wrapper holds one widget, so a
// pointer on it is over that widget's panel.
export function panelRoot(target) {
const host = panelHost(target);
if (!host) return null;
let root = null;
for (let element = target; element && element !== host; element = element.parentElement) {
if (isOurs(element)) root = element;
}
if (!root && target === host && target.matches?.(".dom-widget")) {
root = Array.from(host.children ?? []).find(isOurs) ?? null;
}
return root;
}
// A drag that carries files, from the file manager or the desktop, or a card
// from ComfyUI's Assets panel, which names a picture it made ("... [output]")
// instead of carrying the file. A row being dragged inside a panel carries
// neither and stays the panel's own.
export function dragCarriesFiles(event) {
const types = event?.dataTransfer?.types;
if (!types) return false;
return Array.from(types).some((type) => type === "Files" || type === "application/x-comfy-asset-info");
}
// Does a file dropped here have to be handed to its node by the pack? The
// classic renderer lays a panel over the canvas (inside a .dom-widget). The
// canvas asks the node under the pointer whether it takes a dragged file, but
// it never sees a drag that is over a panel, so a picture dropped on a
// loader's preview missed the node and ComfyUI added a Load Image node for it
// instead. Nodes 2.0 draws the whole node as one element, which hands the
// drop to its node already.
export function panelNeedsDropHelp(target) {
return Boolean(target?.closest?.(".dom-widget") && panelRoot(target));
}
// Keys pressed in one of the pack's fields. A field keeps the keys it types
// and edits with, so the canvas never acts on them (Delete, Ctrl + A). The
// app's own shortcuts go on to ComfyUI, as they do from its text boxes:
// Ctrl + Enter queues a run, Ctrl + S saves the workflow.
const FIELD_EDIT_KEYS = new Set([
"a", "c", "v", "x", "z", "y", "backspace", "delete",
"arrowleft", "arrowright", "arrowup", "arrowdown", "home", "end",
]);
export function isAppShortcut(event) {
if (!event || !(event.ctrlKey || event.metaKey)) return false;
return !FIELD_EDIT_KEYS.has(String(event.key ?? "").toLowerCase());
}
// Ctrl + Up and Ctrl + Down in a prompt box are ComfyUI's own shortcut for the
// weight of the selected words: (word:1.1). ComfyUI listens for them on the
// window, so a box that holds a prompt has to let those two keys go on.
export function isWeightShortcut(event) {
if (!event || !(event.ctrlKey || event.metaKey)) return false;
const key = String(event.key ?? "").toLowerCase();
return key === "arrowup" || key === "arrowdown";
}
// For a field's keydown handler: the key stops at the field unless it is an
// app shortcut. A box that holds a prompt passes `weights: true`, and the
// two weight keys go on to ComfyUI as well.
export function keepKeyInField(event, { weights = false } = {}) {
if (weights && isWeightShortcut(event)) return;
if (!isAppShortcut(event)) event.stopPropagation();
}