Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/renderer/core/layout/store/layoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,15 @@ class LayoutStoreImpl implements LayoutStore {
return this.currentActor
}

/**
* Clean up refs and triggers for a node when its Vue component unmounts.
* This should be called from the component's onUnmounted hook.
*/
cleanupNodeRef(nodeId: NodeId): void {
this.nodeRefs.delete(nodeId)
this.nodeTriggers.delete(nodeId)
}

/**
* Initialize store with existing nodes
*/
Expand All @@ -964,8 +973,10 @@ class LayoutStoreImpl implements LayoutStore {
): void {
this.ydoc.transact(() => {
this.ynodes.clear()
this.nodeRefs.clear()
this.nodeTriggers.clear()
// Note: We intentionally do NOT clear nodeRefs and nodeTriggers here.
// Vue components may already hold references to these refs, and clearing
// them would break the reactivity chain. The refs will be reused when
// nodes are recreated, and stale refs will be cleaned up over time.
this.spatialIndex.clear()
this.linkSegmentSpatialIndex.clear()
this.slotSpatialIndex.clear()
Expand Down Expand Up @@ -995,6 +1006,9 @@ class LayoutStoreImpl implements LayoutStore {
// Add to spatial index
this.spatialIndex.insert(layout.id, layout.bounds)
})

// Trigger all existing refs to notify Vue of the new data
this.nodeTriggers.forEach((trigger) => trigger())
}, 'initialization')
}

Expand Down Expand Up @@ -1085,8 +1099,10 @@ class LayoutStoreImpl implements LayoutStore {
if (!this.ynodes.has(operation.nodeId)) return

this.ynodes.delete(operation.nodeId)
this.nodeRefs.delete(operation.nodeId)
this.nodeTriggers.delete(operation.nodeId)
// Note: We intentionally do NOT delete nodeRefs and nodeTriggers here.
// During undo/redo, Vue components may still hold references to the old ref.
// If we delete the trigger, Vue won't be notified when the node is re-created.
// The trigger will be called in finalizeOperation to notify Vue of the change.

// Remove from spatial index
this.spatialIndex.remove(operation.nodeId)
Expand Down
7 changes: 6 additions & 1 deletion src/renderer/extensions/vueNodes/layout/useNodeLayout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { computed, toValue } from 'vue'
import { computed, onUnmounted, toValue } from 'vue'
import type { MaybeRefOrGetter } from 'vue'

import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
Expand All @@ -17,6 +17,11 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
// Get the customRef for this node (shared write access)
const layoutRef = layoutStore.getNodeLayoutRef(nodeId)

// Clean up refs and triggers when Vue component unmounts
onUnmounted(() => {
layoutStore.cleanupNodeRef(nodeId)
})

// Computed properties for easy access
const position = computed(() => {
const layout = layoutRef.value
Expand Down
Loading