1515 */
1616
1717import { useEffect , useRef , useState } from 'react'
18- import Draggable , { ControlPosition , DraggableData } from 'react-draggable'
19- import { DraggableWrapperProps , DraggablePositionVariant } from './types'
20- import { useWindowSize } from '../Hooks'
18+ import Draggable , { ControlPosition } from 'react-draggable'
19+
20+ import { DEVTRON_BASE_MAIN_ID } from '@Shared/constants'
21+
2122import { MAX_Z_INDEX } from '../Constants'
23+ import { useWindowSize } from '../Hooks'
24+ import { DraggablePositionVariant , DraggableWrapperProps } from './types'
2225
2326/**
2427 * TODO: import it as lazy, after it is supported in common
2528 * 1. If using react select please use menuPlacement='auto'
2629 * 2. dragSelector will be used to identify the grabbable button that will grab the div to drag
27- * 3. parentRef is the reference point from which we will derive the base top:0 ,left: 0 position
30+ * 3. The wrapper is positioned at the viewport's top-left (top: 0, left: 0) using fixed positioning; parentRef is an optional
31+ * reference that may be used for position calculations but is not the base origin for the coordinate system.
2832 */
29- export default function DraggableWrapper ( {
33+ const DraggableWrapper = ( {
3034 children,
3135 zIndex = MAX_Z_INDEX ,
3236 positionVariant,
3337 dragSelector,
3438 parentRef,
35- boundaryGap = 16 ,
39+ boundaryGap = { x : 16 , y : 16 } ,
3640 childDivProps = { } ,
37- layoutFixDelta = 0 ,
38- } : DraggableWrapperProps ) {
41+ } : DraggableWrapperProps ) => {
3942 const windowSize = useWindowSize ( )
4043 const nodeRef = useRef < HTMLDivElement > ( null )
4144
42- const [ position , setPosition ] = useState < ControlPosition > ( {
43- x : 0 ,
44- y : 0 ,
45- } )
45+ // letting the dom render the element without displaying it so that we know it's dimensions
46+ const [ initialRenderDone , setInitialRenderDone ] = useState ( false )
4647
47- const getDefaultPosition = ( positionVariant : DraggablePositionVariant ) : ControlPosition => {
48+ const getDefaultPosition = ( ) : ControlPosition => {
4849 // if this return x: 0, y: 0 then it will be top left corner of parentDiv
4950 const parentRect =
5051 parentRef ?. current ?. getBoundingClientRect ( ) ??
@@ -60,73 +61,62 @@ export default function DraggableWrapper({
6061
6162 switch ( positionVariant ) {
6263 case DraggablePositionVariant . PARENT_BOTTOM_CENTER : {
63- // currently at parentRect.x and need to start to the center of its width and half of node should lie on left of center and other half on right
64- const x = ( parentRect . width - nodeRefWidth ) / 2
65- // TODO (v3): Temp fix. Revisit
66- const parentRectTop = parentRect . top > 0 ? parentRect . top : layoutFixDelta
67- // currently at parentRect.y now parent height can be greater than windowSize.height so taking min
68- // subtracting parentRect.top since window height already contains that
69- const baseY =
70- parentRect . height > windowSize . height ? windowSize . height - parentRectTop : parentRect . height
71- const y = baseY - nodeRefHeight - boundaryGap
64+ // center div to middle of the parent rect and then add the left offset of the parent rect
65+ const x = ( parentRect . width - nodeRefWidth ) / 2 + parentRect . left
66+ if ( parentRect . height > windowSize . height ) {
67+ // since the parent itself overflows, we use windowSize for calculations
68+ return { x , y : windowSize . height - boundaryGap . y - nodeRefHeight }
69+ }
70+ // y = parentRect.bottom will place the widget at the extreme bottom of the parent,
71+ // therefore need to offset it to the top by boundary and its own height
72+ const y = parentRect . bottom - nodeRefHeight - boundaryGap . y
7273 return { x, y }
7374 }
7475 case DraggablePositionVariant . SCREEN_BOTTOM_RIGHT : {
75- const x = windowSize . width - parentRect . left - nodeRefWidth - boundaryGap
76- const y = windowSize . height - parentRect . top - nodeRefHeight - boundaryGap
76+ // x = windowSize.width will place the widget at the extreme right,
77+ // therefore need to offset it to the left by boundary and its own width
78+ const x = windowSize . width - nodeRefWidth - boundaryGap . x
79+ // y = windowSize.height will place the widget at the extreme bottom,
80+ // therefore need to offset it to the top by boundary and its own height
81+ const y = windowSize . height - nodeRefHeight - boundaryGap . y
7782
7883 return { x, y }
7984 }
8085 // Add more cases for other variants if needed
8186 default : {
82- // Since need node to be in center of screen so subtracting width/2 by left of parentRect it will start the node from center but want node's midpoint at center so subtracting node's width from it.
83- const x = windowSize . width / 2 - parentRect . left - nodeRefWidth / 2
84- // subtracting top since windowSize already contains that
85- const y = windowSize . height - parentRect . top - nodeRefHeight - boundaryGap
87+ // we need to first place the start of the widget at (windowSize.width / 2)
88+ // followed by moving it half of its own width to the left such that center of widget
89+ // aligns with the central axis of the screen
90+ const x = ( windowSize . width - nodeRefWidth ) / 2
91+ // y = windowSize.height will place the widget at the extreme bottom,
92+ // therefore need to offset it to the top by boundary and its own height
93+ const y = windowSize . height - nodeRefHeight - boundaryGap . y
8694
8795 return { x, y }
8896 }
8997 }
9098 }
9199
92- // On change of windowSize we will reset the position to default
93100 useEffect ( ( ) => {
94- const defaultPosition = getDefaultPosition ( positionVariant )
95- setPosition ( defaultPosition )
96- } , [ nodeRef , positionVariant , windowSize ] )
97-
98- // Would be called on drag and will not update the state if the new position is out of window screen
99- function handlePositionChange ( e , data : DraggableData ) {
100- const offsetX = parentRef ?. current ?. getBoundingClientRect ( ) . left ?? 0
101- const offsetY = parentRef ?. current ?. getBoundingClientRect ( ) . top ?? 0
102-
103- const nodeRefHeight = nodeRef . current ?. getBoundingClientRect ( ) . height ?? 0
104- const nodeRefWidth = nodeRef . current ?. getBoundingClientRect ( ) . width ?? 0
105-
106- if (
107- offsetX + data . x + nodeRefWidth + boundaryGap > windowSize . width ||
108- offsetY + data . y + nodeRefHeight + boundaryGap > windowSize . height ||
109- offsetX + data . x < 0 ||
110- offsetY + data . y < 0
111- ) {
112- return
113- }
114-
115- setPosition ( {
116- x : data . x ,
117- y : data . y ,
118- } )
119- }
101+ // make the element visible after the initial render
102+ setInitialRenderDone ( true )
103+ } , [ ] )
120104
121105 return (
122106 // Since we are using position fixed so we need to disable click on the div so that it does not interfere with the click of other elements
123107 < div
124- className = " dc__position-fixed dc__disable-click"
108+ className = { ` dc__position-fixed dc__disable-click dc__top-0 dc__left-0 ${ initialRenderDone ? '' : 'dc__visibility-hidden' } ` }
125109 style = { {
126110 zIndex,
127111 } }
128112 >
129- < Draggable handle = { dragSelector } nodeRef = { nodeRef } position = { position } onDrag = { handlePositionChange } >
113+ < Draggable
114+ key = { `${ windowSize . height } -${ windowSize . width } -${ initialRenderDone } ` }
115+ handle = { dragSelector }
116+ defaultPosition = { getDefaultPosition ( ) }
117+ bounds = { `#${ DEVTRON_BASE_MAIN_ID } ` }
118+ nodeRef = { nodeRef }
119+ >
130120 < div
131121 ref = { nodeRef }
132122 { ...childDivProps }
@@ -141,3 +131,5 @@ export default function DraggableWrapper({
141131 </ div >
142132 )
143133}
134+
135+ export default DraggableWrapper
0 commit comments