// Show/hide standard canvas widgets in place. Hiding keeps the widget (and // its serialized value) in the widgets array — only its rendering collapses — // so widgets_values order never changes and saved workflows stay compatible. // Pure widget-object surgery, no DOM: testable under node:test. // // Two renderers, two switches. The classic canvas lays widgets out by // computeSize / computeLayoutSize and paints them with draw, so those are // zeroed. The Nodes 2.0 renderer ignores all of that and filters its widget // list on `widget.options.hidden` instead, so that flag is set as well - a // panel's hidden storage widget stays hidden whichever renderer is on. const STASH = "__ausbossVisibilityStash"; // Collapse a widget to zero height. Returns true when this call changed // visibility, false when it was already hidden (callers use the return to // skip a needless node resize). export function hideWidget(widget) { if (!widget) return false; if (widget[STASH]) { // Already hidden: re-assert the renderer flag, which the frontend // drops whenever it rebuilds the options object from the node spec. if (widget.options) widget.options.hidden = true; return false; } let options = widget.options ?? {}; widget[STASH] = { computeSize: widget.computeSize, computeLayoutSize: widget.computeLayoutSize, draw: widget.draw, hidden: widget.hidden, optionsHidden: options.hidden, optionsDescriptor: Object.getOwnPropertyDescriptor(widget, "options"), }; widget.hidden = true; options.hidden = true; // The frontend swaps a widget's options object after creation (the // after-generate control does, and so does a workflow restore), which // would drop the flag. An accessor keeps re-applying it to whatever // object is assigned, so the widget stays hidden under Nodes 2.0. try { Object.defineProperty(widget, "options", { configurable: true, enumerable: true, get: () => options, set: (next) => { options = next ?? {}; options.hidden = true; }, }); } catch { widget.options = options; } widget.computeSize = () => [0, -4]; widget.computeLayoutSize = () => ({ minWidth: 0, minHeight: 0 }); widget.draw = () => {}; return true; } // Restore a widget hidden by hideWidget. Returns true when this call changed // visibility, false when the widget was never hidden. export function showWidget(widget) { const stash = widget?.[STASH]; if (!stash) return false; widget.computeSize = stash.computeSize; widget.computeLayoutSize = stash.computeLayoutSize; widget.draw = stash.draw; widget.hidden = stash.hidden; const options = widget.options; try { delete widget.options; // drop the accessor, then put a plain property back } catch { // Not configurable: the accessor stays and keeps the flag; nothing to do. } if (stash.optionsDescriptor && "value" in stash.optionsDescriptor) { Object.defineProperty(widget, "options", { ...stash.optionsDescriptor, value: options }); } else { widget.options = options; } if (widget.options) { if (stash.optionsHidden === undefined) delete widget.options.hidden; else widget.options.hidden = stash.optionsHidden; } widget[STASH] = undefined; return true; } // Drive a widget toward a target visibility; true when anything changed. export function setWidgetVisible(widget, visible) { return visible ? showWidget(widget) : hideWidget(widget); } // Mark inputs hidden in the node DEFINITION, before any widget exists. // The frontend rebuilds a widget's options from this spec at several // points (workflow restore, the after-generate control, the Nodes 2.0 // mount), so a flag written on a live widget is not enough on its own - // a storage widget a panel always drives is hidden at the source, and // hideWidget on the live widget covers the classic canvas layout. // // Never throws: a renderer that hands out frozen definitions gets a copied // entry where it allows one and is otherwise left alone - the live-widget // path still hides the widget on the classic canvas. export function hideInputsInDef(nodeData, names) { const groups = nodeData?.input; if (!groups || typeof groups !== "object") return 0; let marked = 0; for (const name of names) { for (const group of ["required", "optional"]) { const spec = groups[group]?.[name]; if (!Array.isArray(spec)) continue; const options = spec[1] && typeof spec[1] === "object" ? spec[1] : {}; if (options.hidden === true) { marked += 1; continue; } try { options.hidden = true; if (spec[1] !== options) spec[1] = options; if (spec[1]?.hidden === true) { marked += 1; continue; } } catch { // frozen options: fall through to replacing the entry } try { groups[group][name] = [spec[0], { ...options, hidden: true }, ...spec.slice(2)]; if (groups[group][name]?.[1]?.hidden === true) marked += 1; } catch { // frozen group: nothing more to do here } } } return marked; }