import React , { useContext , useEffect , useLayoutEffect , useRef , useState } from 'react' ; import { xarrowPropsType } from '../types' ; import useXarrowProps from './useXarrowProps' ; import { XarrowContext } from '../Xwrapper' ; import XarrowPropTypes from './propTypes' ; import { getPosition } from './utils/GetPosition' ; const log = console . log ; const Xarrow = ( props ) => { const mainRef = useRef ( { svgRef : useRef < SVGSVGElement > ( null ) , lineRef : useRef < SVGPathElement > ( null ) , headRef : useRef < SVGElement > ( null ) , tailRef : useRef < SVGElement > ( null ) , lineDrawAnimRef : useRef < SVGElement > ( null ) , lineDashAnimRef : useRef < SVGElement > ( null ) , headOpacityAnimRef : useRef < SVGElement > ( null ) , } ) ; const { svgRef , lineRef , headRef , tailRef , lineDrawAnimRef , lineDashAnimRef , headOpacityAnimRef } = mainRef . current ; useContext ( XarrowContext ) ; const xProps = useXarrowProps ( props , mainRef . current ) ; const [ propsRefs ] = xProps ; let { labels , lineColor , headColor , tailColor , strokeWidth , showHead , showTail , dashness , headShape , tailShape , showXarrow , animateDrawing , zIndex , passProps , arrowBodyProps , arrowHeadProps , arrowTailProps , SVGcanvasProps , divContainerProps , divContainerStyle , SVGcanvasStyle , _debug , shouldUpdatePosition , } = propsRefs ; animateDrawing = props . animateDrawing as number ; const [ drawAnimEnded , setDrawAnimEnded ] = useState ( ! animateDrawing ) ; const [ , setRender ] = useState ( { } ) ; const forceRerender = ( ) => setRender ( { } ) ; const [ st , setSt ] = useState ( { cx0 : 0 , cy0 : 0 , cw : 0 , ch : 0 , x1 : 0 , y1 : 0 , x2 : 0 , y2 : 0 , dx : 0 , dy : 0 , absDx : 0 , absDy : 0 , cpx1 : 0 , cpy1 : 0 , cpx2 : 0 , cpy2 : 0 , headOrient : 0 , tailOrient : 0 , arrowHeadOffset : { x : 0 , y : 0 } , arrowTailOffset : { x : 0 , y : 0 } , headOffset : 0 , excRight : 0 , excLeft : 0 , excUp : 0 , excDown : 0 , startPoints : [ ] , endPoints : [ ] , mainDivPos : { x : 0 , y : 0 } , xSign : 1 , ySign : 1 , lineLength : 0 , fHeadSize : 1 , fTailSize : 1 , arrowPath : `` , labelStartPos : { x : 0 , y : 0 } , labelMiddlePos : { x : 0 , y : 0 } , labelEndPos : { x : 0 , y : 0 } , } ) ; /**
     * The Main logic of path calculation for the arrow.
     * calculate new path, adjusting canvas, and set state based on given properties.
     * */ useLayoutEffect ( ( ) => { if ( shouldUpdatePosition . current ) { const pos = getPosition ( xProps , mainRef ) ; setSt ( pos ) ; shouldUpdatePosition . current = false ; } } ) ; const xOffsetHead = st . x2 - st . arrowHeadOffset . x ; const yOffsetHead = st . y2 - st . arrowHeadOffset . y ; const xOffsetTail = st . x1 - st . arrowTailOffset . x ; const yOffsetTail = st . y1 - st . arrowTailOffset . y ; let dashoffset = dashness . strokeLen + dashness . nonStrokeLen ; let animDirection = 1 ; if ( dashness . animation < 0 ) { dashness . animation *= - 1 ; animDirection = - 1 ; } let dashArray , animation , animRepeatCount , animStartValue , animEndValue = 0 ; if ( animateDrawing && drawAnimEnded == false ) { if ( typeof animateDrawing === 'boolean' ) animateDrawing = 1 ; animation = animateDrawing + 's' ; dashArray = st . lineLength ; animStartValue = st . lineLength ; animRepeatCount = 1 ; if ( animateDrawing < 0 ) { [ animStartValue , animEndValue ] = [ animEndValue , animStartValue ] ; animation = animateDrawing * - 1 + 's' ; } } else { dashArray = `${ dashness . strokeLen } ${ dashness . nonStrokeLen }` ; animation = `${ 1 / dashness . animation }s` ; animStartValue = dashoffset * animDirection ; animRepeatCount = 'indefinite' ; animEndValue = 0 ; } useLayoutEffect ( ( ) => { if ( lineRef . current ) setSt ( ( prevSt ) => ( { ... prevSt , lineLength : lineRef . current ?. getTotalLength ( ) ?? 0 } ) ) ; } , [ lineRef . current ] ) ; useEffect ( ( ) => { const monitorDOMchanges = ( ) => { window . addEventListener ( 'resize' , forceRerender ) ; const handleDrawAmimEnd = ( ) => { setDrawAnimEnded ( true ) ; headOpacityAnimRef . current ?. beginElement ( ) ; lineDashAnimRef . current ?. beginElement ( ) ; } ; const handleDrawAmimBegin = ( ) => ( headRef . current . style . opacity = '0' ) ; if ( lineDrawAnimRef . current && headRef . current ) { lineDrawAnimRef . current . addEventListener ( 'endEvent' , handleDrawAmimEnd ) ; lineDrawAnimRef . current . addEventListener ( 'beginEvent' , handleDrawAmimBegin ) ; } return ( ) => { window . removeEventListener ( 'resize' , forceRerender ) ; if ( lineDrawAnimRef . current ) { lineDrawAnimRef . current . removeEventListener ( 'endEvent' , handleDrawAmimEnd ) ; if ( headRef . current ) lineDrawAnimRef . current . removeEventListener ( 'beginEvent' , handleDrawAmimBegin ) ; } } ; } ; const cleanMonitorDOMchanges = monitorDOMchanges ( ) ; return ( ) => { setDrawAnimEnded ( false ) ; cleanMonitorDOMchanges ( ) ; } ; } , [ showXarrow ] ) ; return ( < div { ... divContainerProps } style = { { position : 'absolute' , zIndex , ... divContainerStyle } } > { showXarrow ? ( < > < svg ref = { svgRef } width = { st . cw } height = { st . ch } style = { { position : 'absolute' , left : st . cx0 , top : st . cy0 , pointerEvents : 'none' , border : _debug ? '1px dashed yellow' : null , ... SVGcanvasStyle , } } overflow = "auto" { ... SVGcanvasProps } > { } < path ref = { lineRef } d = { st . arrowPath } stroke = { lineColor } strokeDasharray = { dashArray } strokeWidth = { strokeWidth } fill = "transparent" pointerEvents = "visibleStroke" { ... ( passProps as any ) } { ... arrowBodyProps } > < > { drawAnimEnded ? ( < > { } { dashness . animation ? ( < animate ref = { lineDashAnimRef } attributeName = "stroke-dashoffset" values = { `${ dashoffset * animDirection };0` } dur = { `${ 1 / dashness . animation }s` } repeatCount = "indefinite" / > ) : null } < />) : (<> { } { animateDrawing ? ( < animate ref = { lineDrawAnimRef } id = { `svgEndAnimate` } attributeName = "stroke-dashoffset" values = { `${ animStartValue };${ animEndValue }` } dur = { animation } repeatCount = { animRepeatCount } / > ) : null } < />)} < /> < /path> { } { showTail ? ( < g fill = { tailColor } pointerEvents = "auto" transform = { `translate(${ xOffsetTail },${ yOffsetTail }) rotate(${ st . tailOrient }) scale(${ st . fTailSize })` } { ... ( passProps as any ) } { ... arrowTailProps } > { tailShape . svgElem } < /g>) : null} { } { showHead ? ( < g ref = { headRef as any } fill = { headColor } pointerEvents = "auto" transform = { `translate(${ xOffsetHead },${ yOffsetHead }) rotate(${ st . headOrient }) scale(${ st . fHeadSize })` } opacity = { animateDrawing && ! drawAnimEnded ? 0 : 1 } { ... ( passProps as any ) } { ... arrowHeadProps } > < animate ref = { headOpacityAnimRef } dur = { '0.4' } attributeName = "opacity" from = "0" to = "1" begin = { `indefinite` } repeatCount = "0" fill = "freeze" / > { headShape . svgElem } < /g>) : null} { } { _debug ? ( < > { } < circle r = "5" cx = { st . cpx1 } cy = { st . cpy1 } fill = "green" / > < circle r = "5" cx = { st . cpx2 } cy = { st . cpy2 } fill = "blue" / > { } < rect x = { st . excLeft } y = { st . excUp } width = { st . absDx } height = { st . absDy } fill = "none" stroke = "pink" strokeWidth = "2px" / > < />) : null} < /svg> { labels . start ? ( < div style = { { transform : st . dx < 0 ? 'translate(-100% , -50%)' : 'translate(-0% , -50%)' , width : 'max-content' , position : 'absolute' , left : st . cx0 + st . labelStartPos . x , top : st . cy0 + st . labelStartPos . y - strokeWidth - 5 , } } > { labels . start } < /div>) : null} { labels . middle ? ( < div style = { { display : 'table' , width : 'max-content' , transform : 'translate(-50% , -50%)' , position : 'absolute' , left : st . cx0 + st . labelMiddlePos . x , top : st . cy0 + st . labelMiddlePos . y , } } > { labels . middle } < /div>) : null} { labels . end ? ( < div style = { { transform : st . dx > 0 ? 'translate(-100% , -50%)' : 'translate(-0% , -50%)' , width : 'max-content' , position : 'absolute' , left : st . cx0 + st . labelEndPos . x , top : st . cy0 + st . labelEndPos . y + strokeWidth + 5 , } } > { labels . end } < /div>) : null} { _debug ? ( < > { } { [ ... st . startPoints , ... st . endPoints ] . map ( ( p , i ) => { return ( < div key = { i } style = { { background : 'gray' , opacity : 0.5 , borderRadius : '50%' , transform : 'translate(-50%, -50%)' , height : 5 , width : 5 , position : 'absolute' , left : p . x - st . mainDivPos . x , top : p . y - st . mainDivPos . y , } } />); } ) } < />) : null} < />) : null} < /div>); } ; Xarrow . propTypes = XarrowPropTypes ; export default Xarrow ;