@@ -8,8 +8,38 @@ import { isPortConnectable } from "../../core/port/connectivity/connectableTypes
88import { PortView } from "./PortView" ;
99import { useOptionalRenderers } from "../../contexts/RendererContext" ;
1010import { hasPortIdChanged } from "../../core/port/identity/comparators" ;
11+ import { NodeCanvasContext } from "../../contexts/composed/canvas/viewport/context" ;
12+ import { NodeEditorContext } from "../../contexts/composed/node-editor/context" ;
13+ import { useExternalStoreSelector } from "../../hooks/useExternalStoreSelector" ;
1114import styles from "./NodePortsRenderer.module.css" ;
1215
16+ // Stable fallback functions for when context is not available
17+ const noopUnsubscribe = ( ) => { } ;
18+ const noopSubscribe = ( ) => noopUnsubscribe ;
19+ const defaultGetState = ( ) => ( { viewport : { scale : 1 } } ) ;
20+ const selectScale = ( state : { viewport : { scale : number } } ) => state . viewport . scale ;
21+
22+ /**
23+ * Hook to determine if port labels should be visible based on zoom level.
24+ * Returns true (show labels) if context is not available, for backwards compatibility.
25+ */
26+ const useShowPortLabels = ( ) : boolean => {
27+ const canvasContext = React . useContext ( NodeCanvasContext ) ;
28+ const editorContext = React . useContext ( NodeEditorContext ) ;
29+
30+ const scale = useExternalStoreSelector (
31+ canvasContext ?. store . subscribe ?? noopSubscribe ,
32+ canvasContext ?. store . getState ?? defaultGetState ,
33+ selectScale ,
34+ ) ;
35+
36+ if ( ! canvasContext || ! editorContext ) {
37+ return true ;
38+ }
39+
40+ return scale >= editorContext . settings . portLabelVisibilityThreshold ;
41+ } ;
42+
1343export type NodePortsRendererProps = {
1444 ports : Port [ ] ;
1545 onPortPointerDown ?: ( e : React . PointerEvent , port : Port ) => void ;
@@ -26,9 +56,10 @@ export type NodePortsRendererProps = {
2656} ;
2757
2858/**
29- * Renders ports for a node
59+ * Pure component that renders ports for a node.
60+ * Does not subscribe to any context - relies on parent to provide showLabels.
3061 */
31- const NodePortsRendererComponent : React . FC < NodePortsRendererProps > = ( {
62+ const NodePortsRendererPure : React . FC < NodePortsRendererProps & { showLabels : boolean } > = ( {
3263 ports,
3364 onPortPointerDown,
3465 onPortPointerUp,
@@ -41,6 +72,7 @@ const NodePortsRendererComponent: React.FC<NodePortsRendererProps> = ({
4172 connectablePorts,
4273 connectingPortId,
4374 candidatePortId,
75+ showLabels,
4476} ) => {
4577 const renderers = useOptionalRenderers ( ) ;
4678 const PortComponent = renderers ?. port ?? PortView ;
@@ -68,18 +100,39 @@ const NodePortsRendererComponent: React.FC<NodePortsRendererProps> = ({
68100 isCandidate = { candidatePortId === port . id }
69101 isHovered = { hoveredPort ?. id === port . id }
70102 isConnected = { connectedPortIds ?. has ( port . id ) }
103+ showLabel = { showLabels }
71104 />
72105 ) ;
73106 } ) }
74107 </ div >
75108 ) ;
76109} ;
77110
111+ /**
112+ * Wrapper component that derives showLabels from context.
113+ * Use this when you need automatic zoom-based label visibility.
114+ */
115+ const NodePortsRendererWithAutoLabels : React . FC < NodePortsRendererProps > = ( props ) => {
116+ const showLabels = useShowPortLabels ( ) ;
117+ return < NodePortsRendererPure { ...props } showLabels = { showLabels } /> ;
118+ } ;
119+
78120// Temporary debug flag - set to true to enable detailed re-render logging
79121const DEBUG_NODEPORTSRENDERER_RERENDERS = false ;
80122
123+ // Props type with optional showLabels for backwards compatibility
124+ export type NodePortsRendererPropsWithLabels = NodePortsRendererProps & { showLabels ?: boolean } ;
125+
81126// Memoized version with custom comparison
82- export const NodePortsRenderer = React . memo ( NodePortsRendererComponent , ( prevProps , nextProps ) => {
127+ export const NodePortsRenderer = React . memo (
128+ ( props : NodePortsRendererPropsWithLabels ) => {
129+ // Use showLabels from props if provided, otherwise derive from context
130+ if ( props . showLabels !== undefined ) {
131+ return < NodePortsRendererPure { ...props } showLabels = { props . showLabels } /> ;
132+ }
133+ return < NodePortsRendererWithAutoLabels { ...props } /> ;
134+ } ,
135+ ( prevProps : NodePortsRendererPropsWithLabels , nextProps : NodePortsRendererPropsWithLabels ) => {
83136 // Get nodeId for debugging (from first port if available)
84137 const nodeId = prevProps . ports ?. [ 0 ] ?. nodeId || nextProps . ports ?. [ 0 ] ?. nodeId || "unknown" ;
85138 const debugLog = ( reason : string , details ?: Record < string , unknown > ) => {
@@ -128,10 +181,15 @@ export const NodePortsRenderer = React.memo(NodePortsRendererComponent, (prevPro
128181 } ) ;
129182 return false ;
130183 }
184+ if ( prevProps . showLabels !== nextProps . showLabels ) {
185+ debugLog ( "showLabels changed" , { prev : prevProps . showLabels , next : nextProps . showLabels } ) ;
186+ return false ;
187+ }
131188
132189 // Event handlers are assumed to be stable (useCallback)
133190 if ( DEBUG_NODEPORTSRENDERER_RERENDERS ) {
134191 console . log ( `[NodePortsRenderer:${ nodeId } ] Skipped re-render (props are equal)` ) ;
135192 }
136193 return true ;
137- } ) ;
194+ } ,
195+ ) ;
0 commit comments