From dbacbc548d9ffc3abda29e62d84655905a119263 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Tue, 9 Sep 2025 17:05:40 -0700 Subject: [PATCH] Revert "refactor(vue-nodes): typed TransformState InjectionKey, safer ResizeObserver sizing, centralized slot tracking, and small readability updates" This reverts commit 428752619c2761a6cf10450124a634be7eae667e. --- src/renderer/core/layout/TransformPane.vue | 3 +- src/renderer/core/layout/injectionKeys.ts | 18 -- .../layout/slots/useDomSlotRegistration.ts | 229 ++++++++++++++++++ src/renderer/core/layout/store/layoutStore.ts | 33 +-- .../vueNodes/components/InputSlot.vue | 18 +- .../vueNodes/components/OutputSlot.vue | 18 +- .../composables/useSlotElementTracking.ts | 199 --------------- .../composables/useVueNodeResizeTracking.ts | 102 +++----- .../vueNodes/layout/useNodeLayout.ts | 8 +- 9 files changed, 293 insertions(+), 335 deletions(-) delete mode 100644 src/renderer/core/layout/injectionKeys.ts create mode 100644 src/renderer/core/layout/slots/useDomSlotRegistration.ts delete mode 100644 src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts diff --git a/src/renderer/core/layout/TransformPane.vue b/src/renderer/core/layout/TransformPane.vue index 34e22c244..2f623257c 100644 --- a/src/renderer/core/layout/TransformPane.vue +++ b/src/renderer/core/layout/TransformPane.vue @@ -16,7 +16,6 @@ import { computed, provide } from 'vue' import { useCanvasTransformSync } from '@/composables/graph/useCanvasTransformSync' import { useTransformSettling } from '@/composables/graph/useTransformSettling' import { LGraphCanvas } from '@/lib/litegraph/src/litegraph' -import { TransformStateKey } from '@/renderer/core/layout/injectionKeys' import { useTransformState } from '@/renderer/core/layout/useTransformState' interface TransformPaneProps { @@ -40,7 +39,7 @@ const { isTransforming: isInteracting } = useTransformSettling(canvasElement, { trackPan: true }) -provide(TransformStateKey, { +provide('transformState', { camera, canvasToScreen, screenToCanvas, diff --git a/src/renderer/core/layout/injectionKeys.ts b/src/renderer/core/layout/injectionKeys.ts deleted file mode 100644 index 48306b063..000000000 --- a/src/renderer/core/layout/injectionKeys.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { InjectionKey } from 'vue' - -import type { Point } from '@/renderer/core/layout/types' - -export interface TransformState { - screenToCanvas: (p: Point) => Point - canvasToScreen: (p: Point) => Point - camera?: { x: number; y: number; z: number } - isNodeInViewport?: ( - nodePos: ArrayLike, - nodeSize: ArrayLike, - viewport: { width: number; height: number }, - margin?: number - ) => boolean -} - -export const TransformStateKey: InjectionKey = - Symbol('transformState') diff --git a/src/renderer/core/layout/slots/useDomSlotRegistration.ts b/src/renderer/core/layout/slots/useDomSlotRegistration.ts new file mode 100644 index 000000000..94a1f09e5 --- /dev/null +++ b/src/renderer/core/layout/slots/useDomSlotRegistration.ts @@ -0,0 +1,229 @@ +/** + * DOM-based slot registration with performance optimization + * + * Measures the actual DOM position of a Vue slot connector and registers it + * into the LayoutStore so hit-testing and link rendering use the true position. + * + * Performance strategy: + * - Cache slot offset relative to node (avoids DOM reads during drag) + * - No measurements during pan/zoom (camera transforms don't change canvas coords) + * - Batch DOM reads via requestAnimationFrame + * - Only remeasure on structural changes (resize, collapse, LOD) + */ +import { + type Ref, + type WatchStopHandle, + nextTick, + onMounted, + onUnmounted, + ref, + watch +} from 'vue' + +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { layoutStore } from '@/renderer/core/layout/store/layoutStore' +import type { Point as LayoutPoint } from '@/renderer/core/layout/types' + +import { getSlotKey } from './slotIdentifier' + +export type TransformState = { + screenToCanvas: (p: LayoutPoint) => LayoutPoint +} + +// Shared RAF queue for batching measurements +const measureQueue = new Set<() => void>() +let rafId: number | null = null +// Track mounted components to prevent execution on unmounted ones +const mountedComponents = new WeakSet() + +function scheduleMeasurement(fn: () => void) { + measureQueue.add(fn) + if (rafId === null) { + rafId = requestAnimationFrame(() => { + rafId = null + const batch = Array.from(measureQueue) + measureQueue.clear() + batch.forEach((measure) => measure()) + }) + } +} + +const cleanupFunctions = new WeakMap< + Ref, + { + stopWatcher?: WatchStopHandle + handleResize?: () => void + } +>() + +interface SlotRegistrationOptions { + nodeId: string + slotIndex: number + isInput: boolean + element: Ref + transform?: TransformState +} + +export function useDomSlotRegistration(options: SlotRegistrationOptions) { + const { nodeId, slotIndex, isInput, element: elRef, transform } = options + + // Early return if no nodeId + if (!nodeId || nodeId === '') { + return { + remeasure: () => {} + } + } + const slotKey = getSlotKey(nodeId, slotIndex, isInput) + // Track if this component is mounted + const componentToken = {} + + // Cached offset from node position (avoids DOM reads during drag) + const cachedOffset = ref(null) + const lastMeasuredBounds = ref(null) + + // Measure DOM and cache offset (expensive, minimize calls) + const measureAndCacheOffset = () => { + // Skip if component was unmounted + if (!mountedComponents.has(componentToken)) return + + const el = elRef.value + if (!el || !transform?.screenToCanvas) return + + const rect = el.getBoundingClientRect() + + // Skip if bounds haven't changed significantly (within 0.5px) + if (lastMeasuredBounds.value) { + const prev = lastMeasuredBounds.value + if ( + Math.abs(rect.left - prev.left) < 0.5 && + Math.abs(rect.top - prev.top) < 0.5 && + Math.abs(rect.width - prev.width) < 0.5 && + Math.abs(rect.height - prev.height) < 0.5 + ) { + return // No significant change - skip update + } + } + + lastMeasuredBounds.value = rect + + // Center of the visual connector (dot) in screen coords + const centerScreen = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + } + const centerCanvas = transform.screenToCanvas(centerScreen) + + // Cache offset from node position for fast updates during drag + const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value + if (nodeLayout) { + cachedOffset.value = { + x: centerCanvas.x - nodeLayout.position.x, + y: centerCanvas.y - nodeLayout.position.y + } + } + + updateSlotPosition(centerCanvas) + } + + // Fast update using cached offset (no DOM read) + const updateFromCachedOffset = () => { + if (!cachedOffset.value) { + // No cached offset yet, need to measure + scheduleMeasurement(measureAndCacheOffset) + return + } + + const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value + if (!nodeLayout) { + return + } + + // Calculate absolute position from node position + cached offset + const centerCanvas = { + x: nodeLayout.position.x + cachedOffset.value.x, + y: nodeLayout.position.y + cachedOffset.value.y + } + + updateSlotPosition(centerCanvas) + } + + // Update slot position in layout store + const updateSlotPosition = (centerCanvas: LayoutPoint) => { + const size = LiteGraph.NODE_SLOT_HEIGHT + const half = size / 2 + + layoutStore.updateSlotLayout(slotKey, { + nodeId, + index: slotIndex, + type: isInput ? 'input' : 'output', + position: { x: centerCanvas.x, y: centerCanvas.y }, + bounds: { + x: centerCanvas.x - half, + y: centerCanvas.y - half, + width: size, + height: size + } + }) + } + + onMounted(async () => { + // Mark component as mounted + mountedComponents.add(componentToken) + + // Initial measure after mount + await nextTick() + measureAndCacheOffset() + + // Subscribe to node position changes for fast cached updates + const nodeRef = layoutStore.getNodeLayoutRef(nodeId) + + const stopWatcher = watch( + nodeRef, + (newLayout) => { + if (newLayout) { + // Node moved/resized - update using cached offset + updateFromCachedOffset() + } + }, + { immediate: false } + ) + + // Store cleanup functions without type assertions + const cleanup = cleanupFunctions.get(elRef) || {} + cleanup.stopWatcher = stopWatcher + + // Window resize - remeasure as viewport changed + const handleResize = () => { + scheduleMeasurement(measureAndCacheOffset) + } + window.addEventListener('resize', handleResize, { passive: true }) + cleanup.handleResize = handleResize + cleanupFunctions.set(elRef, cleanup) + }) + + onUnmounted(() => { + // Mark component as unmounted + mountedComponents.delete(componentToken) + + // Clean up watchers and listeners + const cleanup = cleanupFunctions.get(elRef) + if (cleanup) { + if (cleanup.stopWatcher) cleanup.stopWatcher() + if (cleanup.handleResize) { + window.removeEventListener('resize', cleanup.handleResize) + } + cleanupFunctions.delete(elRef) + } + + // Remove from layout store + layoutStore.deleteSlotLayout(slotKey) + + // Remove from measurement queue if pending + measureQueue.delete(measureAndCacheOffset) + }) + + return { + // Expose for forced remeasure on structural changes + remeasure: () => scheduleMeasurement(measureAndCacheOffset) + } +} diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index 633712f15..385e09bcd 100644 --- a/src/renderer/core/layout/store/layoutStore.ts +++ b/src/renderer/core/layout/store/layoutStore.ts @@ -456,20 +456,6 @@ class LayoutStoreImpl implements LayoutStore { const existing = this.slotLayouts.get(key) if (existing) { - // Short-circuit if nothing changed to avoid spatial index churn - if ( - existing.nodeId === layout.nodeId && - existing.index === layout.index && - existing.type === layout.type && - existing.position.x === layout.position.x && - existing.position.y === layout.position.y && - existing.bounds.x === layout.bounds.x && - existing.bounds.y === layout.bounds.y && - existing.bounds.width === layout.bounds.width && - existing.bounds.height === layout.bounds.height - ) { - return - } // Update spatial index this.slotSpatialIndex.update(key, layout.bounds) } else { @@ -1457,26 +1443,9 @@ class LayoutStoreImpl implements LayoutStore { const ynode = this.ynodes.get(nodeId) if (!ynode) continue - // Short-circuit when bounds are unchanged to avoid churn - const currentBounds = this.getNodeField(ynode, 'bounds') - const sameBounds = - currentBounds.x === bounds.x && - currentBounds.y === bounds.y && - currentBounds.width === bounds.width && - currentBounds.height === bounds.height - if (sameBounds) continue - this.spatialIndex.update(nodeId, bounds) ynode.set('bounds', bounds) - - // Keep size in sync with bounds - const currentSize = this.getNodeField(ynode, 'size') - if ( - currentSize.width !== bounds.width || - currentSize.height !== bounds.height - ) { - ynode.set('size', { width: bounds.width, height: bounds.height }) - } + ynode.set('size', { width: bounds.width, height: bounds.height }) } }, this.currentActor) diff --git a/src/renderer/extensions/vueNodes/components/InputSlot.vue b/src/renderer/extensions/vueNodes/components/InputSlot.vue index 78d9ebe57..124597d6b 100644 --- a/src/renderer/extensions/vueNodes/components/InputSlot.vue +++ b/src/renderer/extensions/vueNodes/components/InputSlot.vue @@ -32,6 +32,7 @@ import { type ComponentPublicInstance, computed, + inject, onErrorCaptured, ref, watchEffect @@ -41,7 +42,10 @@ import { useErrorHandling } from '@/composables/useErrorHandling' import { getSlotColor } from '@/constants/slotColors' import { INodeSlot, LGraphNode } from '@/lib/litegraph/src/litegraph' // DOM-based slot registration for arbitrary positioning -import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking' +import { + type TransformState, + useDomSlotRegistration +} from '@/renderer/core/layout/slots/useDomSlotRegistration' import SlotConnectionDot from './SlotConnectionDot.vue' @@ -71,6 +75,11 @@ onErrorCaptured((error) => { // Get slot color based on type const slotColor = computed(() => getSlotColor(props.slotData.type)) +const transformState = inject( + 'transformState', + undefined +) + const connectionDotRef = ref | null>(null) @@ -83,10 +92,11 @@ watchEffect(() => { slotElRef.value = el || null }) -useSlotElementTracking({ +useDomSlotRegistration({ nodeId: props.nodeId ?? '', - index: props.index, + slotIndex: props.index, isInput: true, - element: slotElRef + element: slotElRef, + transform: transformState }) diff --git a/src/renderer/extensions/vueNodes/components/OutputSlot.vue b/src/renderer/extensions/vueNodes/components/OutputSlot.vue index cecdc72d6..e83019aa9 100644 --- a/src/renderer/extensions/vueNodes/components/OutputSlot.vue +++ b/src/renderer/extensions/vueNodes/components/OutputSlot.vue @@ -33,6 +33,7 @@ import { type ComponentPublicInstance, computed, + inject, onErrorCaptured, ref, watchEffect @@ -42,7 +43,10 @@ import { useErrorHandling } from '@/composables/useErrorHandling' import { getSlotColor } from '@/constants/slotColors' import type { INodeSlot, LGraphNode } from '@/lib/litegraph/src/litegraph' // DOM-based slot registration for arbitrary positioning -import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking' +import { + type TransformState, + useDomSlotRegistration +} from '@/renderer/core/layout/slots/useDomSlotRegistration' import SlotConnectionDot from './SlotConnectionDot.vue' @@ -73,6 +77,11 @@ onErrorCaptured((error) => { // Get slot color based on type const slotColor = computed(() => getSlotColor(props.slotData.type)) +const transformState = inject( + 'transformState', + undefined +) + const connectionDotRef = ref | null>(null) @@ -85,10 +94,11 @@ watchEffect(() => { slotElRef.value = el || null }) -useSlotElementTracking({ +useDomSlotRegistration({ nodeId: props.nodeId ?? '', - index: props.index, + slotIndex: props.index, isInput: false, - element: slotElRef + element: slotElRef, + transform: transformState }) diff --git a/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts b/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts deleted file mode 100644 index 7367604c7..000000000 --- a/src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Centralized Slot Element Tracking - * - * Registers slot connector DOM elements per node, measures their canvas-space - * positions in a single batched pass, and caches offsets so that node moves - * update slot positions without DOM reads. - */ -import { type Ref, inject, nextTick, onMounted, onUnmounted, watch } from 'vue' - -import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import { TransformStateKey } from '@/renderer/core/layout/injectionKeys' -import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier' -import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import type { Point } from '@/renderer/core/layout/types' - -type SlotEntry = { - el: HTMLElement - index: number - isInput: boolean - cachedOffset?: { x: number; y: number } -} - -type NodeEntry = { - nodeId: string - screenToCanvas?: (p: Point) => Point - slots: Map - stopWatch?: () => void -} - -// Registry of nodes and their slots -const nodeRegistry = new Map() - -// RAF batching -const pendingNodes = new Set() -let rafId: number | null = null - -function scheduleNodeMeasure(nodeId: string) { - pendingNodes.add(nodeId) - if (rafId == null) { - rafId = requestAnimationFrame(() => { - rafId = null - runBatchedMeasure() - }) - } -} - -function runBatchedMeasure() { - if (pendingNodes.size === 0) return - - // Read container origin once - const container = document.getElementById('graph-canvas-container') - const originRect = container?.getBoundingClientRect() - const originLeft = originRect?.left ?? 0 - const originTop = originRect?.top ?? 0 - - for (const nodeId of Array.from(pendingNodes)) { - pendingNodes.delete(nodeId) - const node = nodeRegistry.get(nodeId) - if (!node) continue - if (!node.screenToCanvas) continue - const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value - if (!nodeLayout) continue - - for (const [slotKey, entry] of node.slots) { - const rect = entry.el.getBoundingClientRect() - const centerScreen = { - x: rect.left + rect.width / 2 - originLeft, - y: rect.top + rect.height / 2 - originTop - } - const centerCanvas = node.screenToCanvas(centerScreen) - - // Cache offset relative to node position for fast updates later - entry.cachedOffset = { - x: centerCanvas.x - nodeLayout.position.x, - y: centerCanvas.y - nodeLayout.position.y - } - - // Persist layout in canvas coordinates - const size = LiteGraph.NODE_SLOT_HEIGHT - const half = size / 2 - layoutStore.updateSlotLayout(slotKey, { - nodeId, - index: entry.index, - type: entry.isInput ? 'input' : 'output', - position: { x: centerCanvas.x, y: centerCanvas.y }, - bounds: { - x: centerCanvas.x - half, - y: centerCanvas.y - half, - width: size, - height: size - } - }) - } - } -} - -function updateNodeSlotsFromCache(nodeId: string) { - const node = nodeRegistry.get(nodeId) - if (!node) return - const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value - if (!nodeLayout) return - - for (const [slotKey, entry] of node.slots) { - if (!entry.cachedOffset) { - // schedule a remeasure to seed offset - scheduleNodeMeasure(nodeId) - continue - } - const centerCanvas = { - x: nodeLayout.position.x + entry.cachedOffset.x, - y: nodeLayout.position.y + entry.cachedOffset.y - } - const size = LiteGraph.NODE_SLOT_HEIGHT - const half = size / 2 - layoutStore.updateSlotLayout(slotKey, { - nodeId, - index: entry.index, - type: entry.isInput ? 'input' : 'output', - position: { x: centerCanvas.x, y: centerCanvas.y }, - bounds: { - x: centerCanvas.x - half, - y: centerCanvas.y - half, - width: size, - height: size - } - }) - } -} - -export function useSlotElementTracking(options: { - nodeId: string - index: number - isInput: boolean - element: Ref -}) { - const { nodeId, index, isInput, element } = options - - // Get transform utilities from TransformPane - const transformState = inject(TransformStateKey) - - onMounted(async () => { - if (!nodeId) return - await nextTick() - const el = element.value - if (!el) return - - // Ensure node entry - let node = nodeRegistry.get(nodeId) - if (!node) { - node = { - nodeId, - screenToCanvas: transformState?.screenToCanvas, - slots: new Map() - } - nodeRegistry.set(nodeId, node) - // Subscribe once per node to layout changes for fast cached updates - const nodeRef = layoutStore.getNodeLayoutRef(nodeId) - const stop = watch( - nodeRef, - (newLayout, oldLayout) => { - if (newLayout && oldLayout) { - // Update from cache on any position/size change - updateNodeSlotsFromCache(nodeId) - } - }, - { flush: 'post' } - ) - node.stopWatch = () => stop() - } - - // Register slot - const slotKey = getSlotKey(nodeId, index, isInput) - node.slots.set(slotKey, { el, index, isInput }) - - // Seed measurement - scheduleNodeMeasure(nodeId) - }) - - onUnmounted(() => { - if (!nodeId) return - const node = nodeRegistry.get(nodeId) - if (!node) return - - // Remove this slot from registry and layout - const slotKey = getSlotKey(nodeId, index, isInput) - node.slots.delete(slotKey) - layoutStore.deleteSlotLayout(slotKey) - - // If node has no more slots, clean up - if (node.slots.size === 0) { - if (node.stopWatch) node.stopWatch() - nodeRegistry.delete(nodeId) - } - }) - - return { - remeasure: () => scheduleNodeMeasure(nodeId) - } -} diff --git a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts index c4b51c57f..843cf5de1 100644 --- a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts +++ b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts @@ -8,20 +8,11 @@ * Supports different element types (nodes, slots, widgets, etc.) with * customizable data attributes and update handlers. */ -import { getCurrentInstance, inject, onMounted, onUnmounted } from 'vue' +import { getCurrentInstance, onMounted, onUnmounted } from 'vue' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import { TransformStateKey } from '@/renderer/core/layout/injectionKeys' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' -import type { Point } from '@/renderer/core/layout/types' import type { Bounds, NodeId } from '@/renderer/core/layout/types' -// Per-element conversion context -const elementConversion = new WeakMap< - HTMLElement, - { screenToCanvas?: (p: Point) => Point } ->() - /** * Configuration for different types of tracked elements */ @@ -53,20 +44,14 @@ const trackingConfigs: Map = new Map([ // Single ResizeObserver instance for all Vue elements const resizeObserver = new ResizeObserver((entries) => { - // Group updates by type, then flush via each config's handler + // Group updates by element type const updatesByType = new Map>() - // Read container origin once per batch to avoid repeated layout reads - const container = document.getElementById('graph-canvas-container') - const originRect = container?.getBoundingClientRect() - const originLeft = originRect?.left ?? 0 - const originTop = originRect?.top ?? 0 - for (const entry of entries) { if (!(entry.target instanceof HTMLElement)) continue const element = entry.target - // Identify type + id via config dataAttribute + // Find which type this element belongs to let elementType: string | undefined let elementId: string | undefined @@ -81,54 +66,31 @@ const resizeObserver = new ResizeObserver((entries) => { if (!elementType || !elementId) continue - // Use contentBoxSize when available; fall back to contentRect for older engines/tests - const contentBox = Array.isArray(entry.contentBoxSize) - ? entry.contentBoxSize[0] - : { - inlineSize: entry.contentRect.width, - blockSize: entry.contentRect.height - } - const width = contentBox.inlineSize - const height = contentBox.blockSize - - // Screen-space rect + const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0] const rect = element.getBoundingClientRect() - let bounds: Bounds = { x: rect.left, y: rect.top, width, height } - // Convert to canvas space and adjust for title band when possible - const ctx = elementConversion.get(element) - if (ctx?.screenToCanvas) { - const topLeftCanvas = ctx.screenToCanvas({ - x: bounds.x - originLeft, - y: bounds.y - originTop - }) - const dimCanvas = ctx.screenToCanvas({ - x: bounds.width, - y: bounds.height - }) - const originCanvas = ctx.screenToCanvas({ x: 0, y: 0 }) - const canvasWidth = Math.max(0, dimCanvas.x - originCanvas.x) - const canvasHeight = Math.max(0, dimCanvas.y - originCanvas.y) - bounds = { - x: topLeftCanvas.x, - y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT, - width: canvasWidth, - height: Math.max(0, canvasHeight - LiteGraph.NODE_TITLE_HEIGHT) - } + const bounds: Bounds = { + x: rect.left, + y: rect.top, + width, + height: height-LiteGraph.NODE_TITLE_HEIGHT } - let updates = updatesByType.get(elementType) - if (!updates) { - updates = [] - updatesByType.set(elementType, updates) + if (!updatesByType.has(elementType)) { + updatesByType.set(elementType, []) + } + const updates = updatesByType.get(elementType) + if (updates) { + updates.push({ id: elementId, bounds }) } - updates.push({ id: elementId, bounds }) } - // Flush per-type + // Process updates by type for (const [type, updates] of updatesByType) { const config = trackingConfigs.get(type) - if (config && updates.length) config.updateHandler(updates) + if (config && updates.length > 0) { + config.updateHandler(updates) + } } }) @@ -157,23 +119,16 @@ export function useVueElementTracking( appIdentifier: string, trackingType: string ) { - // For canvas-space conversion: provided by TransformPane - const transformState = inject(TransformStateKey) - onMounted(() => { const element = getCurrentInstance()?.proxy?.$el if (!(element instanceof HTMLElement) || !appIdentifier) return const config = trackingConfigs.get(trackingType) - if (!config) return // Set the data attribute expected by the RO pipeline for this type - element.dataset[config.dataAttribute] = appIdentifier - // Remember transformer for this element - if (transformState?.screenToCanvas) { - elementConversion.set(element, { - screenToCanvas: transformState.screenToCanvas - }) + if (config) { + // Set the appropriate data attribute + element.dataset[config.dataAttribute] = appIdentifier + resizeObserver.observe(element) } - resizeObserver.observe(element) }) onUnmounted(() => { @@ -181,11 +136,10 @@ export function useVueElementTracking( if (!(element instanceof HTMLElement)) return const config = trackingConfigs.get(trackingType) - if (!config) return - - // Remove the data attribute and observer - delete element.dataset[config.dataAttribute] - resizeObserver.unobserve(element) - elementConversion.delete(element) + if (config) { + // Remove the data attribute + delete element.dataset[config.dataAttribute] + resizeObserver.unobserve(element) + } }) } diff --git a/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts b/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts index b70940470..407a14243 100644 --- a/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts +++ b/src/renderer/extensions/vueNodes/layout/useNodeLayout.ts @@ -6,7 +6,6 @@ */ import { computed, inject } from 'vue' -import { TransformStateKey } from '@/renderer/core/layout/injectionKeys' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { LayoutSource, type Point } from '@/renderer/core/layout/types' @@ -20,7 +19,12 @@ export function useNodeLayout(nodeId: string) { const mutations = useLayoutMutations() // Get transform utilities from TransformPane if available - const transformState = inject(TransformStateKey) + const transformState = inject('transformState') as + | { + canvasToScreen: (point: Point) => Point + screenToCanvas: (point: Point) => Point + } + | undefined // Get the customRef for this node (shared write access) const layoutRef = store.getNodeLayoutRef(nodeId)