import { Box , Point , Diff , BoundingBox } from "./types" ; export const SNAP_DISTANCE = 15 ; export const top = ( box ) => box . y ; export const bottom = ( box ) => box . y + box . height ; export const left = ( box ) => box . x ; export const right = ( box ) => box . x + box . width ; export const near = ( a , b ) => Math . abs ( a - b ) < SNAP_DISTANCE ; export const overlapX = ( a , b ) => left ( a ) <= right ( b ) + SNAP_DISTANCE && left ( b ) <= right ( a ) + SNAP_DISTANCE ; export const overlapY = ( a , b ) => top ( a ) <= bottom ( b ) + SNAP_DISTANCE && top ( b ) <= bottom ( a ) + SNAP_DISTANCE ; export const snap = ( boxA , boxB ) => { let x , y ; if ( overlapY ( boxA , boxB ) ) { if ( near ( left ( boxA ) , right ( boxB ) ) ) { x = right ( boxB ) ; } else if ( near ( right ( boxA ) , left ( boxB ) ) ) { x = left ( boxB ) - boxA . width ; } else if ( near ( left ( boxA ) , left ( boxB ) ) ) { x = left ( boxB ) ; } else if ( near ( right ( boxA ) , right ( boxB ) ) ) { x = right ( boxB ) - boxA . width ; } } if ( overlapX ( boxA , boxB ) ) { if ( near ( top ( boxA ) , bottom ( boxB ) ) ) { y = bottom ( boxB ) ; } else if ( near ( bottom ( boxA ) , top ( boxB ) ) ) { y = top ( boxB ) - boxA . height ; } else if ( near ( top ( boxA ) , top ( boxB ) ) ) { y = top ( boxB ) ; } else if ( near ( bottom ( boxA ) , bottom ( boxB ) ) ) { y = bottom ( boxB ) - boxA . height ; } } return { x , y } ; } ; export const snapDiff = ( a , b ) : Point => { const newPos = snap ( a , b ) ; return { x : newPos . x === undefined ? 0 : newPos . x - a . x , y : newPos . y === undefined ? 0 : newPos . y - a . y , } ; } ; export const snapDiffManyToMany = ( as , bs ) : Point => { let x = 0 ; let y = 0 ; for ( const a of as ) { for ( const b of bs ) { const diff = snapDiff ( a , b ) ; x = x || diff . x ; y = y || diff . y ; if ( x !== undefined && x > 0 && y !== undefined && y > 0 ) { break ; } } } return { x , y } ; } ; export const snapToMany = ( boxA , otherBoxes ) : Diff => { let x ; let y ; otherBoxes . forEach ( ( boxB ) => { const newPos = snap ( boxA , boxB ) ; x = newPos . x || x ; y = newPos . y || y ; } ) ; return { x , y } ; } ; export const snapWithin = ( boxA , boundingBox ) : Diff => { let x , y ; if ( boxA . x - SNAP_DISTANCE < 0 ) { x = 0 ; } else if ( boxA . x + boxA . width + SNAP_DISTANCE > boundingBox . width ) { x = boundingBox . width - boxA . width ; } if ( boxA . y - SNAP_DISTANCE < 0 ) { y = 0 ; } else if ( boxA . y + boxA . height + SNAP_DISTANCE > boundingBox . height ) { y = boundingBox . height - boxA . height ; } return { x , y } ; } ; export const snapWithinDiff = ( a , b ) => { const newPos = snapWithin ( a , b ) ; return { x : newPos . x === undefined ? 0 : newPos . x - a . x , y : newPos . y === undefined ? 0 : newPos . y - a . y , } ; } ; export const applySnap = ( original , ... snaps ) => snaps . reduce ( ( previous , snapped ) => ( { ... previous , x : typeof snapped . x !== "undefined" ? snapped . x : previous . x , y : typeof snapped . y !== "undefined" ? snapped . y : previous . y , } ) , original ) ; export const boundingBox = ( nodes ) : Box => { const boxes = nodes . slice ( ) ; const firstNode = boxes . pop ( ) ; if ( firstNode == null ) { throw new Error ( "boundingBox must be called with at least one node" ) ; } const bounding = { top : top ( firstNode ) , right : right ( firstNode ) , bottom : bottom ( firstNode ) , left : left ( firstNode ) , } ; boxes . forEach ( ( node ) => { bounding . top = Math . min ( bounding . top , top ( node ) ) ; bounding . right = Math . max ( bounding . right , right ( node ) ) ; bounding . bottom = Math . max ( bounding . bottom , bottom ( node ) ) ; bounding . left = Math . min ( bounding . left , left ( node ) ) ; } ) ; return { x : bounding . left , y : bounding . top , width : bounding . right - bounding . left , height : bounding . bottom - bounding . top , } ; } ; export function traceConnection < B extends Box > ( areConnected ) { return ( candidates , node ) : Set < B > => { const connected = new Set < B > ( ) ; const checkNode = ( n ) => { for ( const candidate of candidates ) { if ( ! connected . has ( candidate ) && areConnected ( candidate , n ) ) { connected . add ( candidate ) ; checkNode ( candidate ) ; } } } ; checkNode ( node ) ; return connected ; } ; } export const applyDiff = ( a , b ) => ( { x : a . x + b . x , y : a . y + b . y , } ) ; export const applyMultipleDiffs = ( initial , ... diffs ) => { const metaDiff = diffs . reduce ( ( m , diff ) => ( { x : m . x === 0 || diff . x === 0 ? m . x + diff . x : Math . min ( m . x , diff . x ) , y : m . y === 0 || diff . y === 0 ? m . y + diff . y : Math . min ( m . y , diff . y ) , } ) ) ; return applyDiff ( initial , metaDiff ) ; } ;