diff --git a/src/composables/graph/useSelectionState.ts b/src/composables/graph/useSelectionState.ts index 23268ba2a6..a42a092c1a 100644 --- a/src/composables/graph/useSelectionState.ts +++ b/src/composables/graph/useSelectionState.ts @@ -1,5 +1,5 @@ import { storeToRefs } from 'pinia' -import { computed } from 'vue' +import { computed, watch } from 'vue' import { useNodeLibrarySidebarTab } from '@/composables/sidebarTabs/useNodeLibrarySidebarTab' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' @@ -94,6 +94,19 @@ export function useSelectionState() { computeSelectionStatesFromNodes(selectedNodes.value) ) + // Keep help panel in sync when it is open and the user changes selection. + watch( + () => nodeDef.value, + (def) => { + if (!nodeHelpStore.isHelpOpen || !def) return + + const currentHelpNode = nodeHelpStore.currentHelpNode + if (currentHelpNode?.nodePath === def.nodePath) return + + nodeHelpStore.openHelp(def) + } + ) + // On-demand computation (non-reactive) so callers can fetch fresh flags const computeSelectionFlags = (): NodeSelectionState => computeSelectionStatesFromNodes(selectedNodes.value) @@ -105,12 +118,11 @@ export function useSelectionState() { const isSidebarActive = sidebarTabStore.activeSidebarTabId === nodeLibraryTabId - const currentHelpNode: any = nodeHelpStore.currentHelpNode + const currentHelpNode = nodeHelpStore.currentHelpNode const isSameNodeHelpOpen = isSidebarActive && nodeHelpStore.isHelpOpen && - currentHelpNode && - currentHelpNode.nodePath === def.nodePath + currentHelpNode?.nodePath === def.nodePath if (isSameNodeHelpOpen) { nodeHelpStore.closeHelp() diff --git a/tests-ui/tests/composables/graph/useSelectionState.test.ts b/tests-ui/tests/composables/graph/useSelectionState.test.ts index ccab3054f7..32daf61590 100644 --- a/tests-ui/tests/composables/graph/useSelectionState.test.ts +++ b/tests-ui/tests/composables/graph/useSelectionState.test.ts @@ -1,6 +1,7 @@ import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, test, vi } from 'vitest' -import { type Ref, ref } from 'vue' +import { nextTick, ref } from 'vue' +import type { Ref } from 'vue' import { useSelectionState } from '@/composables/graph/useSelectionState' import { useNodeLibrarySidebarTab } from '@/composables/sidebarTabs/useNodeLibrarySidebarTab' @@ -267,4 +268,57 @@ describe('useSelectionState', () => { expect(newIsPinned).toBe(false) }) }) + + describe('Help Sync', () => { + test('opens help for newly selected node when help is open', async () => { + const nodeDefStore = useNodeDefStore() as any + nodeDefStore.fromLGraphNode.mockImplementation((node: TestNode) => ({ + nodePath: node.type + })) + + const nodeHelpStore = useNodeHelpStore() as any + nodeHelpStore.isHelpOpen = true + nodeHelpStore.currentHelpNode = { nodePath: 'NodeA' } + + const nodeA = createTestNode({ type: 'NodeA' }) + mockSelectedItems.value = [nodeA] + + useSelectionState() + + const nodeB = createTestNode({ type: 'NodeB' }) + mockSelectedItems.value = [nodeB] + + await nextTick() + + expect(nodeHelpStore.openHelp).toHaveBeenCalledWith({ + nodePath: 'NodeB' + }) + }) + + test('does not reopen help when selection is unchanged or closed', async () => { + const nodeDefStore = useNodeDefStore() as any + nodeDefStore.fromLGraphNode.mockImplementation((node: TestNode) => ({ + nodePath: node.type + })) + + const nodeHelpStore = useNodeHelpStore() as any + const nodeA = createTestNode({ type: 'NodeA' }) + mockSelectedItems.value = [nodeA] + + useSelectionState() + + // Help closed -> no call + nodeHelpStore.isHelpOpen = false + mockSelectedItems.value = [nodeA] + await nextTick() + expect(nodeHelpStore.openHelp).not.toHaveBeenCalled() + + // Help open but same node -> no call + nodeHelpStore.isHelpOpen = true + nodeHelpStore.currentHelpNode = { nodePath: 'NodeA' } + mockSelectedItems.value = [nodeA] + await nextTick() + expect(nodeHelpStore.openHelp).not.toHaveBeenCalled() + }) + }) })