UNPKG

@bpmsoftwaresolutions/renderx-plugins

Version:

RenderX plugins meta-package with unit tests and build + manifest generation

297 lines (283 loc) 10.3 kB
// Pointer-driven drag lifecycle for Canvas UI elements // attachDragHandlers returns an object of event handlers to spread into element props import { ensureCursorStylesInjected } from "../styles/cursors.js"; import { updateInstancePositionCSS } from "../styles/instanceCss.js"; import { DragCoordinator } from "../utils/DragCoordinator.js"; import { overlayArrangement } from "../features/overlay/overlay.arrangement.js"; export function attachDragHandlers(node, deps = {}) { ensureCursorStylesInjected(); const getPromptBook = () => { try { const w = (typeof window !== "undefined" && window) || {}; return w.__rx_prompt_book__ || null; } catch { return null; } }; const getStartPos = () => { // Prefer position from Prompt Book ONLY if the node exists in the store try { const pb = getPromptBook(); const exists = pb?.selectors?.nodeById?.(node.id); if (exists) { const p = pb?.selectors?.positionOf?.(node.id); if (p && typeof p.x === "number" && typeof p.y === "number") { return { x: p.x, y: p.y }; } } } catch {} // Fallback to any persisted baseline (to be removed after full migration) try { const w = (typeof window !== "undefined" && window) || {}; const p = w.__rx_canvas_ui__?.positions?.[node.id]; if (p && typeof p.x === "number" && typeof p.y === "number") { return { x: p.x, y: p.y }; } } catch {} // Default to node's own position return { x: node?.position?.x || 0, y: node?.position?.y || 0, }; }; const playLegacy = (id, payload) => { try { const system = (window && window.renderxCommunicationSystem) || null; const conductor = system && system.conductor; if (conductor && typeof conductor.play === "function") { conductor.play(id, id, payload); } } catch {} }; const playPhase = (channel, action, payload) => { try { const system = (window && window.renderxCommunicationSystem) || null; const conductor = system && system.conductor; if (conductor && typeof conductor.play === "function") { conductor.play(channel, action, payload); } } catch {} }; const getRec = () => { try { const w = (typeof window !== "undefined" && window) || {}; w.__rx_drag = w.__rx_drag || {}; return w.__rx_drag[node.id] || null; } catch { return null; } }; const setRec = (rec) => { try { const w = (typeof window !== "undefined" && window) || {}; w.__rx_drag = w.__rx_drag || {}; w.__rx_drag[node.id] = rec; } catch {} }; return { onPointerEnter: (e) => { try { const rec = getRec(); if (!rec || !rec.active) { // Ensure only hover affordance is present e.currentTarget?.classList?.remove("rx-comp-grabbing"); e.currentTarget?.classList?.add("rx-comp-draggable"); } } catch {} }, onPointerLeave: (e) => { try { const rec = getRec(); if (!rec || !rec.active) { e.currentTarget?.classList?.remove("rx-comp-draggable"); e.currentTarget?.classList?.remove("rx-comp-grabbing"); } } catch {} }, onPointerDown: (e) => { try { e?.stopPropagation?.(); try { // On drag start: switch cursor to grabbing e.currentTarget?.classList?.remove("rx-comp-draggable"); e.currentTarget?.classList?.add("rx-comp-grabbing"); if (e.currentTarget && e.currentTarget.style) { try { e.currentTarget.style.touchAction = "none"; e.currentTarget.style.willChange = "transform"; } catch {} } } catch {} try { e.target?.setPointerCapture?.(e.pointerId); } catch {} const origin = { x: e.clientX || 0, y: e.clientY || 0 }; DragCoordinator.start({ id: node.id, start: getStartPos(), origin, el: e.currentTarget || null, }); setRec({ origin, start: getStartPos(), active: true, lastCursor: origin, rafScheduled: false, el: e.currentTarget || null, }); // Orchestration: broadcast drag start; overlay will react via conductor // Emit both namespaced and base events to support mixed listeners during migration playPhase("Canvas.component-drag-symphony", "start", { elementId: node.id, origin, }); playLegacy("Canvas.component-drag-symphony", { elementId: node.id, origin, }); // Safety: ensure overlay hides even if concertmasters aren't bootstrapped in this test try { const css = overlayArrangement.hideRule(node.id); const id = `overlay-visibility-${node.id}`; let tag = document.getElementById(id); if (!tag) { tag = document.createElement("style"); tag.id = id; document.head.appendChild(tag); } tag.textContent = css; } catch {} } catch {} }, onPointerMove: (e) => { try { try { // Maintain grabbing cursor while dragging; ignore hover-only moves if (e && e.buttons === 1) { e.currentTarget?.classList?.remove("rx-comp-draggable"); e.currentTarget?.classList?.add("rx-comp-grabbing"); } } catch {} const cur = { x: e.clientX || 0, y: e.clientY || 0 }; const rec = getRec(); if (!rec || !rec.active) return; DragCoordinator.move({ id: node.id, cursor: cur, onFrame: ({ dx, dy }) => { // Legacy overlay callback (back-compat) + Orchestration move per frame try { const system = (window && window.renderxCommunicationSystem) || null; const conductor = system && system.conductor; // Also update overlay transform directly for UI tests that assert DOM styles without concertmaster bootstrapping try { const css = overlayArrangement.transformRule(node.id, dx, dy); const id = `overlay-transform-${node.id}`; let tag = document.getElementById(id); if (!tag) { tag = document.createElement("style"); tag.id = id; document.head.appendChild(tag); } tag.textContent = css; } catch {} playPhase("Canvas.component-drag-symphony", "update", { elementId: node.id, delta: { dx, dy }, }); playLegacy("Canvas.component-drag-symphony", { elementId: node.id, delta: { dx, dy }, }); } catch {} }, }); } catch {} }, onPointerUp: (e) => { try { try { e.currentTarget?.classList?.remove("rx-comp-grabbing"); e.currentTarget?.classList?.add("rx-comp-draggable"); if (e.currentTarget && e.currentTarget.style) { try { e.currentTarget.style.willChange = ""; e.currentTarget.style.touchAction = ""; e.currentTarget.style.transform = ""; } catch {} } } catch {} try { e.target?.releasePointerCapture?.(e.pointerId); } catch {} const upClient = { x: e.clientX || 0, y: e.clientY || 0 }; const finalPos = DragCoordinator.end({ id: node.id, upClient, onCommit: (pos) => { try { updateInstancePositionCSS( node.id, String(node.cssClass || node.id || ""), pos.x, pos.y ); } catch {} try { const w = (typeof window !== "undefined" && window) || {}; const pb = w.__rx_prompt_book__ || null; try { pb?.actions?.move?.(node.id, { x: pos.x, y: pos.y }); } catch {} // After store commit, read the canonical position and update per-instance CSS try { const committed = pb?.selectors?.positionOf?.(node.id) || { x: pos.x, y: pos.y, }; updateInstancePositionCSS( node.id, String(node.cssClass || node.id || ""), committed.x, committed.y ); // Also commit overlay base instance CSS for tests that assert DOM state without full concertmaster wiring try { const css = overlayArrangement.instanceRule( node.id, committed, {} ); const id = `overlay-instance-${node.id}`; let tag = document.getElementById(id); if (!tag) { tag = document.createElement("style"); tag.id = id; document.head.appendChild(tag); } tag.textContent = css; } catch {} } catch {} } catch {} }, }); playPhase("Canvas.component-drag-symphony", "end", { elementId: node.id, end: true, }); playLegacy("Canvas.component-drag-symphony", { elementId: node.id, end: true, }); // Clean up overlay visibility tag (show overlay) try { const id = `overlay-visibility-${node.id}`; const tag = document.getElementById(id); if (tag && tag.parentNode) tag.parentNode.removeChild(tag); } catch {} } catch {} }, }; }