11import 'react-grid-layout/css/styles.css' ;
22import './styles' ;
3- import ReactGridLayout , { Layout , ReactGridLayoutProps } from 'react-grid-layout' ;
3+ import ReactGridLayout , { useContainerWidth , LayoutItem } from 'react-grid-layout' ;
44import GridTile , { SetWidgetAttribute } from './GridTile' ;
5- import { useEffect , useMemo , useRef , useState } from 'react' ;
5+ import { useEffect , useMemo , useState } from 'react' ;
66import { isWidgetType } from './utils' ;
77import React from 'react' ;
88import {
@@ -30,8 +30,8 @@ const createSerializableConfig = (config?: WidgetConfiguration) => {
3030// SVG resize handle as inline data URI
3131const resizeHandleSvg = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2IDEuMTQyODZMMTQuODU3MSAwTDAgMTQuODU3MVYxNkgxLjE0Mjg2TDE2IDEuMTQyODZaIiBmaWxsPSIjRDJEMkQyIi8+Cjwvc3ZnPgo=' ;
3232
33- const getResizeHandle = ( resizeHandleAxis : string , ref : React . Ref < HTMLDivElement > ) => (
34- < div ref = { ref } className = { `react-resizable-handle react-resizable-handle-${ resizeHandleAxis } ` } >
33+ const getResizeHandle = ( resizeHandleAxis : string , ref : React . Ref < HTMLElement > ) => (
34+ < div ref = { ref as React . Ref < HTMLDivElement > } className = { `react-resizable-handle react-resizable-handle-${ resizeHandleAxis } ` } >
3535 < img src = { resizeHandleSvg } alt = "Resize handle" />
3636 </ div >
3737 ) ;
@@ -57,6 +57,8 @@ export interface GridLayoutProps {
5757 onDrawerExpandChange ?: ( expanded : boolean ) => void ;
5858 /** Currently active widgets (for tracking) */
5959 onActiveWidgetsChange ?: ( widgetTypes : string [ ] ) => void ;
60+ /** Widget type currently being dragged from drawer */
61+ droppingWidgetType ?: string ;
6062}
6163
6264const LayoutEmptyState = ( {
@@ -100,35 +102,35 @@ const GridLayout = ({
100102 showEmptyState = true ,
101103 onDrawerExpandChange,
102104 onActiveWidgetsChange,
105+ droppingWidgetType,
103106} : GridLayoutProps ) => {
104107 const [ isDragging , setIsDragging ] = useState ( false ) ;
105108 const [ isInitialRender , setIsInitialRender ] = useState ( true ) ;
106109 const [ layoutVariant , setLayoutVariant ] = useState < Variants > ( 'xl' ) ;
107- const [ layoutWidth , setLayoutWidth ] = useState < number > ( 1200 ) ;
108- const layoutRef = useRef < HTMLDivElement > ( null ) ;
110+
111+ // Use v2 hook for container width measurement
112+ const { width : layoutWidth , containerRef, mounted } = useContainerWidth ( ) ;
109113
110- const [ currentDropInItem , setCurrentDropInItem ] = useState < string | undefined > ( ) ;
111114 const [ internalTemplate , setInternalTemplate ] = useState < ExtendedTemplateConfig > ( template ) ;
112115
113116 // Sync external template changes to internal state
114117 useEffect ( ( ) => {
115118 setInternalTemplate ( template ) ;
116119 } , [ template ] ) ;
117120
118- const droppingItemTemplate : ReactGridLayoutProps [ 'droppingItem' ] = useMemo ( ( ) => {
119- if ( currentDropInItem && isWidgetType ( widgetMapping , currentDropInItem ) ) {
120- const widget = widgetMapping [ currentDropInItem ] ;
121+ const droppingItemTemplate = useMemo ( ( ) => {
122+ if ( droppingWidgetType && isWidgetType ( widgetMapping , droppingWidgetType ) ) {
123+ const widget = widgetMapping [ droppingWidgetType ] ;
121124 if ( ! widget ) { return undefined ; }
122125 return {
123126 ...widget . defaults ,
124127 i : droppingElemId ,
125- widgetType : currentDropInItem ,
126- title : 'New title' ,
127- config : createSerializableConfig ( widget . config )
128+ x : 0 ,
129+ y : 0 ,
128130 } ;
129131 }
130132 return undefined ;
131- } , [ currentDropInItem , widgetMapping ] ) ;
133+ } , [ droppingWidgetType , widgetMapping ] ) ;
132134
133135 const setWidgetAttribute : SetWidgetAttribute = ( id , attributeName , value ) => {
134136 const newTemplate = Object . entries ( internalTemplate ) . reduce (
@@ -154,10 +156,11 @@ const GridLayout = ({
154156 onTemplateChange ?.( newTemplate ) ;
155157 } ;
156158
157- const onDrop : ReactGridLayoutProps [ 'onDrop' ] = ( _layout : ExtendedLayoutItem [ ] , layoutItem : ExtendedLayoutItem , event : DragEvent ) => {
158- const data = event . dataTransfer ?. getData ( 'text' ) || '' ;
159+ const onDrop = ( _layout : readonly LayoutItem [ ] , layoutItem : LayoutItem | undefined , event : Event ) => {
160+ if ( ! layoutItem ) return ;
161+ const dragEvent = event as DragEvent ;
162+ const data = dragEvent . dataTransfer ?. getData ( 'text' ) || '' ;
159163 if ( isWidgetType ( widgetMapping , data ) ) {
160- setCurrentDropInItem ( undefined ) ;
161164 const widget = widgetMapping [ data ] ;
162165 if ( ! widget ) { return ; }
163166 const newTemplate = Object . entries ( internalTemplate ) . reduce ( ( acc , [ size , layout ] ) => {
@@ -193,76 +196,70 @@ const GridLayout = ({
193196 onTemplateChange ?.( newTemplate ) ;
194197 analytics ?.( 'widget-layout.widget-add' , { data } ) ;
195198 }
196- event . preventDefault ( ) ;
199+ dragEvent . preventDefault ( ) ;
197200 } ;
198201
199- const onLayoutChange = ( currentLayout : Layout [ ] ) => {
202+ const onLayoutChange = ( currentLayout : readonly LayoutItem [ ] ) => {
200203 if ( isInitialRender ) {
201204 setIsInitialRender ( false ) ;
202205 const activeWidgets = activeLayout . map ( ( item ) => item . widgetType ) ;
203206 onActiveWidgetsChange ?.( activeWidgets ) ;
204207 return ;
205208 }
206- if ( isLayoutLocked || currentDropInItem ) {
209+ if ( isLayoutLocked || droppingWidgetType ) {
207210 return ;
208211 }
209212
210- const newTemplate = extendLayout ( { ...internalTemplate , [ layoutVariant ] : currentLayout } ) ;
213+ // Create mutable copy of readonly layout for extendLayout
214+ const newTemplate = extendLayout ( { ...internalTemplate , [ layoutVariant ] : [ ...currentLayout ] } ) ;
211215 const activeWidgets = activeLayout . map ( ( item ) => item . widgetType ) ;
212216 onActiveWidgetsChange ?.( activeWidgets ) ;
213217
214218 setInternalTemplate ( newTemplate ) ;
215219 onTemplateChange ?.( newTemplate ) ;
216220 } ;
217221
222+ // Update layout variant when container width changes
218223 useEffect ( ( ) => {
219- const currentWidth = layoutRef . current ?. getBoundingClientRect ( ) . width ?? 1200 ;
220- const variant : Variants = getGridDimensions ( currentWidth ) ;
221- setLayoutVariant ( variant ) ;
222- setLayoutWidth ( currentWidth ) ;
223-
224- const observer = new ResizeObserver ( ( entries ) => {
225- if ( ! entries [ 0 ] ) { return ; }
226-
227- const currentWidth = entries [ 0 ] . contentRect . width ;
228- const variant : Variants = getGridDimensions ( currentWidth ) ;
224+ if ( mounted && layoutWidth > 0 ) {
225+ const variant : Variants = getGridDimensions ( layoutWidth ) ;
229226 setLayoutVariant ( variant ) ;
230- setLayoutWidth ( currentWidth ) ;
231- } ) ;
232-
233- if ( layoutRef . current ) {
234- observer . observe ( layoutRef . current ) ;
235227 }
236-
237- return ( ) => {
238- observer . disconnect ( ) ;
239- } ;
240- } , [ ] ) ;
228+ } , [ layoutWidth , mounted ] ) ;
241229
242230 const activeLayout = internalTemplate [ layoutVariant ] || [ ] ;
243231
232+ // Use default width before mount, actual width after
233+ const effectiveWidth = mounted && layoutWidth > 0 ? layoutWidth : 1200 ;
234+
244235 return (
245- < div id = "widget-layout-container" style = { { position : 'relative' } } ref = { layoutRef } >
246- { activeLayout . length === 0 && ! currentDropInItem && showEmptyState && (
236+ < div id = "widget-layout-container" style = { { position : 'relative' } } ref = { containerRef } >
237+ { activeLayout . length === 0 && ! droppingWidgetType && showEmptyState && (
247238 emptyStateComponent || < LayoutEmptyState onDrawerExpandChange = { onDrawerExpandChange } documentationLink = { documentationLink } />
248239 ) }
249- < ReactGridLayout
240+ { mounted && < ReactGridLayout
250241 key = { 'grid-' + layoutVariant }
251- draggableHandle = ".drag-handle"
252242 layout = { internalTemplate [ layoutVariant ] }
253- cols = { columns [ layoutVariant ] }
254- rowHeight = { 56 }
255- width = { layoutWidth }
256- isDraggable = { ! isLayoutLocked }
257- isResizable = { ! isLayoutLocked }
258- resizeHandle = { getResizeHandle as unknown as ReactGridLayoutProps [ 'resizeHandle' ] }
259- resizeHandles = { [ 'sw' , 'nw' , 'se' , 'ne' ] }
243+ width = { effectiveWidth }
260244 droppingItem = { droppingItemTemplate }
261- isDroppable = { ! isLayoutLocked }
245+ gridConfig = { {
246+ cols : columns [ layoutVariant ] ,
247+ rowHeight : 56 ,
248+ } }
249+ dragConfig = { {
250+ handle : '.drag-handle' ,
251+ enabled : ! isLayoutLocked ,
252+ } }
253+ resizeConfig = { {
254+ enabled : ! isLayoutLocked ,
255+ handles : [ 's' , 'w' , 'e' , 'n' , 'sw' , 'nw' , 'se' , 'ne' ] ,
256+ handleComponent : getResizeHandle ,
257+ } }
258+ dropConfig = { {
259+ enabled : ! isLayoutLocked ,
260+ } }
262261 onDrop = { onDrop }
263- onDragStart = { ( ) => setCurrentDropInItem ( undefined ) }
264- useCSSTransforms
265- verticalCompact
262+ onDragStart = { ( ) => { } }
266263 onLayoutChange = { onLayoutChange }
267264 >
268265 { activeLayout
@@ -279,7 +276,7 @@ const GridLayout = ({
279276 isDragging = { isDragging }
280277 setIsDragging = { setIsDragging }
281278 widgetType = { widgetType }
282- widgetConfig = { { ...layoutItem , colWidth : layoutWidth / columns [ layoutVariant ] , config } }
279+ widgetConfig = { { ...layoutItem , colWidth : effectiveWidth / columns [ layoutVariant ] , config } }
283280 setWidgetAttribute = { setWidgetAttribute }
284281 removeWidget = { removeWidget }
285282 analytics = { analytics }
@@ -290,7 +287,7 @@ const GridLayout = ({
290287 ) ;
291288 } )
292289 . filter ( ( layoutItem ) => layoutItem !== null ) }
293- </ ReactGridLayout >
290+ </ ReactGridLayout > }
294291 </ div >
295292 ) ;
296293} ;
0 commit comments