@@ -12,7 +12,7 @@ const ResponsiveGridLayout = WidthProvider(Responsive);
1212
1313interface GridContainerProps {
1414 layouts : Layouts | null ;
15- onLayoutChange : ( layout : Layout [ ] , newLayouts : Layouts ) => void ;
15+ onLayoutChange : ( layout : Layout [ ] , newLayouts : Layouts , metadata ?: { origin ?: { source ?: string } } ) => void ;
1616 onResizeStart ?: ( ) => void ;
1717 onResizeStop ?: ( ) => void ;
1818 viewMode : ViewModeState ;
@@ -35,6 +35,59 @@ export const GridContainer: React.FC<GridContainerProps> = ({
3535 newItemId = null
3636} ) => {
3737 const { selectedItem, setSelectedItem, previewMode } = usePluginStudio ( ) ;
38+ const interactionSourceRef = React . useRef < 'user-drag' | 'user-resize' | 'drop-add' | null > ( null ) ;
39+ const interactionResetTimeoutRef = React . useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
40+
41+ const cancelInteractionReset = React . useCallback ( ( ) => {
42+ if ( interactionResetTimeoutRef . current ) {
43+ clearTimeout ( interactionResetTimeoutRef . current ) ;
44+ interactionResetTimeoutRef . current = null ;
45+ }
46+ } , [ ] ) ;
47+
48+ const scheduleInteractionReset = React . useCallback ( ( delay = 150 ) => {
49+ cancelInteractionReset ( ) ;
50+ interactionResetTimeoutRef . current = setTimeout ( ( ) => {
51+ interactionSourceRef . current = null ;
52+ interactionResetTimeoutRef . current = null ;
53+ } , delay ) ;
54+ } , [ cancelInteractionReset ] ) ;
55+
56+ const handleDragStart = React . useCallback ( ( ) => {
57+ cancelInteractionReset ( ) ;
58+ interactionSourceRef . current = 'user-drag' ;
59+ } , [ cancelInteractionReset ] ) ;
60+
61+ const handleDragStop = React . useCallback ( ( ) => {
62+ scheduleInteractionReset ( ) ;
63+ } , [ scheduleInteractionReset ] ) ;
64+
65+ const handleResizeStartInternal = React . useCallback ( ( ) => {
66+ cancelInteractionReset ( ) ;
67+ interactionSourceRef . current = 'user-resize' ;
68+ onResizeStart ?.( ) ;
69+ } , [ cancelInteractionReset , onResizeStart ] ) ;
70+
71+ const handleResizeStopInternal = React . useCallback ( ( ) => {
72+ onResizeStop ?.( ) ;
73+ scheduleInteractionReset ( ) ;
74+ } , [ onResizeStop , scheduleInteractionReset ] ) ;
75+
76+ React . useEffect ( ( ) => {
77+ if ( newItemId ) {
78+ cancelInteractionReset ( ) ;
79+ interactionSourceRef . current = 'drop-add' ;
80+ scheduleInteractionReset ( 200 ) ;
81+ }
82+ } , [ newItemId , cancelInteractionReset , scheduleInteractionReset ] ) ;
83+
84+ React . useEffect ( ( ) => {
85+ return ( ) => {
86+ if ( interactionResetTimeoutRef . current ) {
87+ clearTimeout ( interactionResetTimeoutRef . current ) ;
88+ }
89+ } ;
90+ } , [ ] ) ;
3891
3992 /**
4093 * Handle item selection
@@ -90,64 +143,58 @@ export const GridContainer: React.FC<GridContainerProps> = ({
90143
91144 // Memoize the layout change handler to prevent unnecessary re-renders
92145 const handleLayoutChange = React . useCallback ( ( currentLayout : Layout [ ] , allLayouts : ReactGridLayouts ) => {
93- // Debounce rapid layout changes from React Grid Layout
94- requestAnimationFrame ( ( ) => {
95- // Convert back to our Layouts type, preserving the original GridItem properties
96- const convertLayoutArray = ( layouts : Layout [ ] | undefined , currentLayouts : ( GridItemType | LayoutItem ) [ ] | undefined ) : ( GridItemType | LayoutItem ) [ ] => {
97- if ( ! layouts ) return [ ] ;
98-
99- return layouts . map ( layout => {
100- // Find the original item to preserve its properties
101- const originalItem = currentLayouts ?. find ( item => item . i === layout . i ) ;
102-
103- if ( originalItem ) {
104- // Only update if position or size actually changed
105- if ( originalItem . x === layout . x &&
106- originalItem . y === layout . y &&
107- originalItem . w === layout . w &&
108- originalItem . h === layout . h ) {
109- return originalItem ; // No change, return original
110- }
111-
112- // Preserve all properties but update position and size
113- return {
114- ...originalItem ,
115- x : layout . x ,
116- y : layout . y ,
117- w : layout . w ,
118- h : layout . h
119- } ;
120- } else {
121- // If original item not found, create a basic LayoutItem
122- return {
123- moduleUniqueId : layout . i ,
124- i : layout . i ,
125- x : layout . x ,
126- y : layout . y ,
127- w : layout . w ,
128- h : layout . h
129- } as LayoutItem ;
146+ const convertLayoutArray = ( candidateLayouts : Layout [ ] | undefined , currentLayouts : ( GridItemType | LayoutItem ) [ ] | undefined ) : ( GridItemType | LayoutItem ) [ ] => {
147+ if ( ! candidateLayouts ) return [ ] ;
148+
149+ return candidateLayouts . map ( layout => {
150+ const originalItem = currentLayouts ?. find ( item => item . i === layout . i ) ;
151+
152+ if ( originalItem ) {
153+ if ( originalItem . x === layout . x &&
154+ originalItem . y === layout . y &&
155+ originalItem . w === layout . w &&
156+ originalItem . h === layout . h ) {
157+ return originalItem ;
130158 }
131- } ) ;
132- } ;
133-
134- const ourLayouts : Layouts = {
135- desktop : convertLayoutArray ( allLayouts . desktop , layouts ?. desktop ) ,
136- tablet : convertLayoutArray ( allLayouts . tablet , layouts ?. tablet ) ,
137- mobile : convertLayoutArray ( allLayouts . mobile , layouts ?. mobile )
138- } ;
139-
140- onLayoutChange ( currentLayout , ourLayouts ) ;
141- } ) ;
159+
160+ return {
161+ ...originalItem ,
162+ x : layout . x ,
163+ y : layout . y ,
164+ w : layout . w ,
165+ h : layout . h
166+ } ;
167+ }
168+
169+ return {
170+ moduleUniqueId : layout . i ,
171+ i : layout . i ,
172+ x : layout . x ,
173+ y : layout . y ,
174+ w : layout . w ,
175+ h : layout . h
176+ } as LayoutItem ;
177+ } ) ;
178+ } ;
179+
180+ const ourLayouts : Layouts = {
181+ desktop : convertLayoutArray ( allLayouts . desktop , layouts ?. desktop ) ,
182+ tablet : convertLayoutArray ( allLayouts . tablet , layouts ?. tablet ) ,
183+ mobile : convertLayoutArray ( allLayouts . mobile , layouts ?. mobile )
184+ } ;
185+
186+ const originSource = interactionSourceRef . current ;
187+ const metadata = originSource ? { origin : { source : originSource } } : undefined ;
188+
189+ onLayoutChange ( currentLayout , ourLayouts , metadata ) ;
142190 } , [ layouts , onLayoutChange ] ) ;
143191
144192 return (
145193 < Box
146194 sx = { {
147- border : '2px dashed rgba(0, 0, 0, 0.1)' ,
148- borderRadius : 2 ,
149195 minHeight : 400 ,
150196 width : viewWidth ,
197+ maxWidth : '100%' ,
151198 mx : 'auto'
152199 } }
153200 >
@@ -168,8 +215,10 @@ export const GridContainer: React.FC<GridContainerProps> = ({
168215 margin = { currentViewModeConfig . margin }
169216 containerPadding = { currentViewModeConfig . padding }
170217 onLayoutChange = { handleLayoutChange }
171- onResizeStart = { onResizeStart }
172- onResizeStop = { onResizeStop }
218+ onResizeStart = { handleResizeStartInternal }
219+ onResizeStop = { handleResizeStopInternal }
220+ onDragStart = { handleDragStart }
221+ onDragStop = { handleDragStop }
173222 isDraggable = { ! previewMode }
174223 isResizable = { ! previewMode }
175224 compactType = "vertical"
@@ -204,4 +253,4 @@ export const GridContainer: React.FC<GridContainerProps> = ({
204253 </ ResponsiveGridLayout >
205254 </ Box >
206255 ) ;
207- } ;
256+ } ;
0 commit comments