9393 type =" target"
9494 />
9595
96- <!-- ValueIndicator of input -->
97- <ValueIndicator
98- v-if =" getSpecialConnection('input')"
99- :edge-id =" getSpecialConnection('input')!.edgeId"
96+ <!-- NodePortal of input -->
97+ <NodePortal
98+ v-if =" inputPortals.has('input')"
10099 mode =" input"
101- :reversed =" isReversed"
102- :source-field-name =" getSpecialConnection('input')!.sourceFieldName"
103- :source-node =" getSpecialConnection('input')!.sourceNode"
100+ :reversed =" isPortalReversed('input')"
101+ :targets =" inputPortals.get('input')!"
104102 />
105103
106104 <div class =" handle-label-wrapper" >
147145 type =" target"
148146 />
149147
150- <!-- ValueIndicator of input fields -->
151- <ValueIndicator
152- v-if =" getSpecialConnection(field.id)"
153- :edge-id =" getSpecialConnection(field.id)!.edgeId"
148+ <!-- NodePortal of input fields -->
149+ <NodePortal
150+ v-if =" inputPortals.has(field.id)"
154151 mode =" input"
155- :reversed =" isReversed"
156- :source-field-name =" getSpecialConnection(field.id)!.sourceFieldName"
157- :source-node =" getSpecialConnection(field.id)!.sourceNode"
152+ :reversed =" isPortalReversed('input')"
153+ :targets =" inputPortals.get(field.id)!"
158154 />
159155
160156 <div class =" handle-label-wrapper" >
213209 type =" source"
214210 />
215211
216- <!-- ValueIndicator of output -->
217- <ValueIndicator
218- v-if =" getSpecialOutputConnection ('output')"
212+ <!-- NodePortal of output -->
213+ <NodePortal
214+ v-if =" outputPortals.has ('output')"
219215 mode =" output"
220- :reversed =" isReversed"
221- :target-field-names =" getSpecialOutputConnection('output')!.targetFieldNames"
222- :target-nodes =" getSpecialOutputConnection('output')!.targetNodes"
216+ :reversed =" isPortalReversed('output')"
217+ :targets =" outputPortals.get('output')!"
223218 />
224219 </div >
225220
245240 type =" source"
246241 />
247242
248- <!-- ValueIndicator of output fields -->
249- <ValueIndicator
250- v-if =" getSpecialOutputConnection (field.id)"
243+ <!-- NodePortal of output fields -->
244+ <NodePortal
245+ v-if =" outputPortals.has (field.id)"
251246 mode =" output"
252- :reversed =" isReversed"
253- :target-field-names =" getSpecialOutputConnection(field.id)!.targetFieldNames"
254- :target-nodes =" getSpecialOutputConnection(field.id)!.targetNodes"
247+ :reversed =" isPortalReversed('output')"
248+ :targets =" outputPortals.get(field.id)!"
255249 />
256250 </div >
257251 </template >
284278</template >
285279
286280<script setup lang="ts">
287- import type { EdgeId , FieldId , FieldName , NodeField , NodeInstance } from ' ../../types'
281+ import type { FieldName , NodeInstance , NonEmptyArray } from ' ../../types'
288282
289283import { computed , watch } from ' vue'
290284import { KTooltip , KButton , KDropdown , KDropdownItem } from ' @kong/kongponents'
@@ -299,19 +293,19 @@ import {
299293} from ' @kong/design-tokens'
300294import { MoreIcon , UnfoldLessIcon , UnfoldMoreIcon , WarningIcon } from ' @kong/icons'
301295import { Handle , Position } from ' @vue-flow/core'
296+ import { isEqual } from ' lodash-es'
302297
303298import english from ' ../../../../../locales/en.json'
304299import { isReadableProperty , isWritableProperty } from ' ../node/property'
305300import { useOptionalFlowStore } from ' ../store/flow'
306301import { getNodeMeta } from ' ../store/helpers'
307302import { useEditorStore } from ' ../store/store'
308- import HandleTwig from ' ./HandleTwig.vue '
303+ import { HOTKEYS } from ' ../constants '
309304import { isImplicitNode } from ' ./node'
310- import NodeBadge from ' ./NodeBadge.vue'
311305import HotkeyLabel from ' ../HotkeyLabel.vue'
312- import { HOTKEYS } from ' ../constants '
313- import { isEqual } from ' lodash-es '
314- import ValueIndicator from ' ./ValueIndicator .vue'
306+ import NodePortal from ' ./NodePortal.vue '
307+ import HandleTwig from ' ./HandleTwig.vue '
308+ import NodeBadge from ' ./NodeBadge .vue'
315309
316310const { data, readonly } = defineProps <{
317311 data: NodeInstance
@@ -400,119 +394,67 @@ const handleTwigColor = computed(() => {
400394 return isImplicit .value ? KUI_COLOR_BACKGROUND_NEUTRAL_STRONG : KUI_COLOR_BACKGROUND_NEUTRAL_WEAKER
401395})
402396
403- // Special input connections (vault, cross-phase, or branch-group-entering)
404- const specialInputConnections = computed (() => {
405- const connections = new Map < string , {
406- fieldId : FieldId | ' input'
407- edgeId : EdgeId
408- field : NodeField | null
409- sourceNode : NodeInstance
410- sourceFieldName?: FieldName
411- }>()
412-
413- const inEdges = getInEdgesByNodeId (data .id )
414-
415- for (const edge of inEdges ) {
416- const sourceNode = getNodeById (edge .source )
417- if (! sourceNode ) continue
418-
419- const isVault = sourceNode .type === ' vault'
420-
421- const isCrossPhase = sourceNode .phase !== data .phase
422- const isEnteringGroup = branchGroups .isEdgeEnteringGroup (sourceNode .id , data .id )
423-
424- if (! isVault && ! isCrossPhase && ! isEnteringGroup ) continue
425-
426- let sourceFieldName: FieldName | undefined
427- if (edge .sourceField ) {
428- const sourceField = sourceNode .fields .output .find (f => f .id === edge .sourceField )
429- sourceFieldName = sourceField ?.name
430- }
431-
432- if (edge .targetField ) {
433- const field = data .fields .input .find (f => f .id === edge .targetField )
434- if (! field ) continue
435-
436- connections .set (edge .targetField , {
437- fieldId: edge .targetField ,
438- edgeId: edge .id ,
439- field ,
440- sourceNode ,
441- sourceFieldName ,
442- })
443- } else {
444- connections .set (' input' , {
445- fieldId: ' input' ,
446- edgeId: edge .id ,
447- field: null ,
448- sourceNode ,
449- sourceFieldName ,
450- })
451- }
397+ /**
398+ * Determines whether the NodePortal needs to flip to the opposite side.
399+ * Inputs inherit the node's reversed flag; outputs take the inverse.
400+ */
401+ function isPortalReversed(mode : ' input' | ' output' ) {
402+ if (mode === ' input' ) {
403+ return isReversed .value
452404 }
405+ return ! isReversed .value
406+ }
453407
454- return connections
455- })
456-
457- // Special output connections (cross-phase or branch-group-entering)
458- const specialOutputConnections = computed (() => {
459- const connections = new Map < string , {
460- fieldId : FieldId | ' output'
461- edgeIds : EdgeId[]
462- field : NodeField | null
463- targetNodes : NodeInstance[]
464- targetFieldNames : Array<FieldName | undefined>
465- }>()
408+ type PortalTarget = { node: NodeInstance , fieldName? : FieldName }
466409
467- const outEdges = getOutEdgesByNodeId (data .id )
410+ function collectPortals(io : ' input' | ' output' ) {
411+ const portals = new Map <string , NonEmptyArray <PortalTarget >>()
412+ const edges = io === ' input' ? getInEdgesByNodeId (data .id ) : getOutEdgesByNodeId (data .id )
468413
469- for (const edge of outEdges ) {
470- const targetNode = getNodeById (edge .target )
414+ for (const edge of edges ) {
415+ const targetNode = getNodeById (io === ' input ' ? edge . source : edge .target )
471416 if (! targetNode ) continue
472417
473- const isCrossPhase = targetNode . phase ! == data . phase
474- const isEnteringGroup = branchGroups . isEdgeEnteringGroup ( data . id , targetNode .id )
475-
476- if ( ! isCrossPhase && ! isEnteringGroup ) continue
477-
478- let targetFieldName : FieldName | undefined
479- if ( edge . targetField ) {
480- const targetField = targetNode . fields . input . find ( f => f .id === edge . targetField )
481- targetFieldName = targetField ?. name
418+ if ( io = == ' input ' ) {
419+ const isVault = targetNode .type === ' vault '
420+ const isCrossPhase = targetNode . phase !== data . phase
421+ const isEnteringGroup = branchGroups . isEdgeEnteringGroup ( targetNode . id , data . id )
422+ if ( ! ( isVault || isCrossPhase || isEnteringGroup )) continue
423+ } else {
424+ const isCrossPhase = targetNode . phase !== data . phase
425+ const isEnteringGroup = branchGroups . isEdgeEnteringGroup ( data .id , targetNode . id )
426+ if ( ! ( isCrossPhase || isEnteringGroup )) continue
482427 }
483428
484- const fieldKey = edge .sourceField || ' output'
485-
486- if (! connections .has (fieldKey )) {
487- const field = edge .sourceField
488- ? data .fields .output .find (f => f .id === edge .sourceField ) || null
489- : null
490-
491- connections .set (fieldKey , {
492- fieldId: fieldKey ,
493- edgeIds: [],
494- field ,
495- targetNodes: [],
496- targetFieldNames: [],
497- })
429+ const key = io === ' input'
430+ ? (edge .targetField || ' input' )
431+ : (edge .sourceField || ' output' )
432+
433+ let fieldName: FieldName | undefined
434+ if (io === ' input' && edge .sourceField ) {
435+ const field = targetNode .fields .output .find (f => f .id === edge .sourceField )
436+ fieldName = field ?.name
437+ } else if (io === ' output' && edge .targetField ) {
438+ const field = targetNode .fields .input .find (f => f .id === edge .targetField )
439+ fieldName = field ?.name
498440 }
499441
500- const connection = connections .get (fieldKey )!
501- connection .edgeIds .push (edge .id )
502- connection .targetNodes .push (targetNode )
503- connection .targetFieldNames .push (targetFieldName )
442+ const targets = portals .get (key )
443+ if (targets ) {
444+ targets .push ({ node: targetNode , fieldName })
445+ } else {
446+ portals .set (key , [{ node: targetNode , fieldName }])
447+ }
504448 }
505449
506- return connections
507- })
508-
509- function getSpecialConnection(fieldId : FieldId | ' input' ) {
510- return specialInputConnections .value .get (fieldId )
450+ return portals
511451}
512452
513- function getSpecialOutputConnection(fieldId : FieldId | ' output' ) {
514- return specialOutputConnections .value .get (fieldId )
515- }
453+ // Input portals (vault, cross-phase, or branch-group-entering)
454+ const inputPortals = computed (() => collectPortals (' input' ))
455+
456+ // Output portals (cross-phase or branch-group-entering)
457+ const outputPortals = computed (() => collectPortals (' output' ))
516458
517459function toggleExpanded(io : ' input' | ' output' ) {
518460 if (! flowStore || flowStore .readonly ) return
0 commit comments