@bpmsoftwaresolutions/renderx-plugins
Version:
RenderX plugins meta-package with unit tests and build + manifest generation
276 lines (270 loc) • 10.7 kB
JavaScript
import {
overlayInjectGlobalCSS,
overlayInjectInstanceCSS,
} from "../utils/styles.js";
import { ResizeCoordinator } from "../utils/ResizeCoordinator.js";
// Ensure global overlay CSS is available as soon as this module is loaded
try {
overlayInjectGlobalCSS();
} catch {}
export function buildOverlayForNode(React, n, key, selectedId) {
if (!(selectedId && (n.id === selectedId || n.elementId === selectedId)))
return null;
try {
overlayInjectGlobalCSS();
const defaults =
(n.component &&
n.component.integration &&
n.component.integration.canvasIntegration) ||
{};
// Try to read committed size from instance CSS so overlay matches on reselect
let w = defaults.defaultWidth;
let h = defaults.defaultHeight;
try {
const cls = String(n.cssClass || n.id || "");
const tagId =
"component-instance-css-" + String(n.id || n.elementId || "");
const tag = document.getElementById(tagId);
const text = (tag && tag.textContent) || "";
const mw = text.match(
new RegExp(`\\.${cls}\\s*\\{[^}]*width\\s*:\\s*([0-9.-]+)px;`, "i")
);
const mh = text.match(
new RegExp(`\\.${cls}\\s*\\{[^}]*height\\s*:\\s*([0-9.-]+)px;`, "i")
);
if (mw) w = parseFloat(mw[1]);
if (mh) h = parseFloat(mh[1]);
} catch {}
overlayInjectInstanceCSS({ id: n.id, position: n.position }, w, h);
} catch {}
const overlayClass = `rx-resize-overlay rx-overlay-${n.id || n.elementId}`;
const handles = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
const elementId = n.id || n.elementId;
const play = (payload) => {
try {
const system = (window && window.renderxCommunicationSystem) || null;
const conductor = system && system.conductor;
if (conductor && typeof conductor.play === "function") {
conductor.play(
"Canvas.component-resize-symphony",
"Canvas.component-resize-symphony",
payload
);
}
} catch {}
};
const getUI = () => {
try {
const w = (typeof window !== "undefined" && window) || {};
return w.__rx_canvas_ui__ || {};
} catch {
return {};
}
};
return React.createElement(
"div",
{ key: `${key}__overlay`, className: overlayClass },
...handles.map((h) =>
React.createElement("div", {
key: `${key}__${h}`,
className: `rx-resize-handle rx-${h}`,
onPointerDown: (e) => {
try {
e && e.stopPropagation && e.stopPropagation();
// Toggle cursor: resizing active
try {
e.currentTarget?.classList?.remove("rx-comp-draggable");
e.currentTarget?.classList?.add("rx-comp-grabbing");
} catch {}
// Pointer capture for reliable move/up
try {
if (
e &&
e.target &&
typeof e.target.setPointerCapture === "function" &&
typeof e.pointerId !== "undefined"
) {
e.target.setPointerCapture(e.pointerId);
}
} catch {}
const origin = { x: e.clientX || 0, y: e.clientY || 0 };
ResizeCoordinator.start({ id: elementId, origin });
// Determine starting box from instance CSS tag (or defaults)
let startW = 0,
startH = 0;
try {
const w = (typeof window !== "undefined" && window) || {};
const cls = String(n.cssClass || n.id || "");
const tagId = "component-instance-css-" + String(elementId || "");
const tag = document.getElementById(tagId);
const text = (tag && tag.textContent) || "";
const mw = text.match(
new RegExp(
`\\.${cls}\\s*\\{[^}]*width\\s*:\\s*([0-9.-]+)px;`,
"i"
)
);
const mh = text.match(
new RegExp(
`\\.${cls}\\s*\\{[^}]*height\\s*:\\s*([0-9.-]+)px;`,
"i"
)
);
if (mw) startW = parseFloat(mw[1]);
if (mh) startH = parseFloat(mh[1]);
if (!startW || !startH) {
const defaults =
n?.component?.integration?.canvasIntegration || {};
startW = startW || defaults.defaultWidth || 0;
startH = startH || defaults.defaultHeight || 0;
}
w.__rx_canvas_ui__ = w.__rx_canvas_ui__ || {};
const baseX =
n && n.position && typeof n.position.x === "number"
? n.position.x
: 0;
const baseY =
n && n.position && typeof n.position.y === "number"
? n.position.y
: 0;
w.__rx_canvas_ui__.__lastW = startW;
w.__rx_canvas_ui__.__lastH = startH;
w.__rx_canvas_ui__.__lastBox = {
x: baseX,
y: baseY,
w: startW,
h: startH,
};
// Keep a fixed baseline for this gesture
w.__rx_canvas_ui__.__resizeBaseline =
w.__rx_canvas_ui__.__resizeBaseline || {};
w.__rx_canvas_ui__.__resizeBaseline[elementId] = {
x: baseX,
y: baseY,
w: startW,
h: startH,
};
// Also pass callbacks + startBox so plugin can compute/rehydrate
const ui = getUI();
play({
elementId,
handle: h,
start: origin,
startBox: { x: baseX, y: baseY, w: startW, h: startH },
onResizeUpdate: ui.onResizeUpdate,
onResizeEnd: ui.onResizeEnd,
});
return; // We already played with proper payload
} catch {}
play({ elementId, handle: h, start: origin });
} catch {}
},
onPointerMove: (e) => {
try {
// Ignore hover after pointerup or non-primary moves
if (!e || e.buttons !== 1) return;
const cursor = { x: e.clientX || 0, y: e.clientY || 0 };
ResizeCoordinator.move({
id: elementId,
cursor,
onFrame: ({ dx, dy }) => {
const ui = getUI();
try {
const base = (ui.__resizeBaseline &&
ui.__resizeBaseline[elementId]) || {
x: 0,
y: 0,
w: Number(ui.__lastW || 0),
h: Number(ui.__lastH || 0),
};
// Derive new box depending on handle semantics
let xNew = base.x;
let yNew = base.y;
let wNew = base.w;
let hNew = base.h;
const dir = String(h || "");
if (dir.includes("e"))
wNew = Math.max(0, Math.round((base.w || 0) + dx));
if (dir.includes("s"))
hNew = Math.max(0, Math.round((base.h || 0) + dy));
if (dir.includes("w")) {
xNew = Math.round((base.x || 0) + dx);
wNew = Math.max(0, Math.round((base.w || 0) - dx));
}
if (dir.match(/(^|[^n])n/)) {
yNew = Math.round((base.y || 0) + dy);
hNew = Math.max(0, Math.round((base.h || 0) - dy));
}
ui.onResizeUpdate?.({
elementId,
box: { x: xNew, y: yNew, w: wNew, h: hNew },
});
} catch {}
try {
// Maintain grabbing cursor while resizing
try {
if (e && e.currentTarget) {
e.currentTarget.classList?.remove("rx-comp-draggable");
e.currentTarget.classList?.add("rx-comp-grabbing");
}
} catch {}
// Also play to plugin, and include baseline + onResizeUpdate to satisfy contract
const base =
(ui.__resizeBaseline && ui.__resizeBaseline[elementId]) ||
null;
// For N/W handles, plugin will compute x/y; include baseline so UI can show left/top changes via onResizeUpdate
play({
elementId,
handle: h,
delta: { dx, dy },
startBox: base || undefined,
onResizeUpdate: ui.onResizeUpdate,
});
} catch {}
},
});
} catch {}
},
onPointerUp: (e) => {
try {
const up = { x: e.clientX || 0, y: e.clientY || 0 };
ResizeCoordinator.end({ id: elementId, upClient: up });
// Restore cursor: back to draggable
try {
e.currentTarget?.classList?.remove("rx-comp-grabbing");
e.currentTarget?.classList?.add("rx-comp-draggable");
} catch {}
// Release pointer capture
try {
if (
e &&
e.target &&
typeof e.target.releasePointerCapture === "function" &&
typeof e.pointerId !== "undefined"
) {
e.target.releasePointerCapture(e.pointerId);
}
} catch {}
const ui = getUI();
try {
if (ui && typeof ui.onResizeEnd === "function" && ui.__lastBox) {
ui.onResizeEnd({ elementId, box: ui.__lastBox });
}
// Clear baseline for element
if (ui && ui.__resizeBaseline && ui.__resizeBaseline[elementId]) {
delete ui.__resizeBaseline[elementId];
}
} catch {}
// Also notify plugin end (constraints/persistence as needed)
play({
elementId,
handle: h,
end: true,
onResizeEnd: ui.onResizeEnd,
});
} catch {}
},
})
)
);
}