11import { Suspense , useCallback , useEffect , useMemo , useRef , useState } from 'react'
22import { useConfirm } from '@/components/ui/confirm-dialog'
33import { Lock , Plus , Settings , Unlock } from 'lucide-react'
4- import { ResponsiveGridLayout } from 'react-grid-layout'
4+ import { GridLayout } from 'react-grid-layout'
55import 'react-grid-layout/css/styles.css'
66import 'react-resizable/css/styles.css'
77import DashboardHeader from './DashboardHeader'
@@ -12,7 +12,14 @@ import DashboardSettingsDialog from './components/DashboardSettingsDialog'
1212import SearchWidgetSettings from './components/SearchWidgetSettings'
1313import { getWidgetMeta , isWidgetCategoryEnabled } from './widgets/registry'
1414import { useModules } from '@/hooks/useModules'
15- import { GRID_ROW_HEIGHT , GRID_MARGIN , computeAvailableRows , fitWidgetHeight } from './utils/grid-utils'
15+ import {
16+ GRID_COLS ,
17+ GRID_ROW_HEIGHT ,
18+ GRID_MARGIN ,
19+ computeAvailableRows ,
20+ fitWidgetHeight ,
21+ mergeLayoutIntoWidgets ,
22+ } from './utils/grid-utils'
1623import { resolveTimeRange , type CalendarPreset } from './utils/resolveTimeRange'
1724import { Button } from '@/components/ui/button'
1825import { Input } from '@/components/ui/input'
@@ -41,10 +48,11 @@ function DashboardGrid() {
4148 const [ containerWidth , setContainerWidth ] = useState ( 0 )
4249 const [ containerHeight , setContainerHeight ] = useState ( 0 )
4350 const widgetsRef = useRef ( widgets )
44- const skipLayoutChangeRef = useRef ( false )
51+ const lockedRef = useRef ( locked )
4552 const { widgetControl } = useModules ( )
4653
4754 useEffect ( ( ) => { widgetsRef . current = widgets } , [ widgets ] )
55+ useEffect ( ( ) => { lockedRef . current = locked } , [ locked ] )
4856
4957 useEffect ( ( ) => {
5058 const el = containerRef . current
@@ -86,41 +94,29 @@ function DashboardGrid() {
8694 }
8795 } ) , [ widgets , locked ] )
8896
89- const handleLayoutChange = useCallback ( ( newLayout ) => {
90- if ( skipLayoutChangeRef . current ) {
91- skipLayoutChangeRef . current = false
92- return
93- }
94- const current = widgetsRef . current
95- let changed = false
96- const updated = current . map ( ( w ) => {
97- const item = newLayout . find ( ( l ) => l . i === w . id )
98- if ( item && ( w . x !== item . x || w . y !== item . y || w . w !== item . w || w . h !== item . h ) ) {
99- changed = true
100- return { ...w , x : item . x , y : item . y , w : item . w , h : item . h }
101- }
102- return w
103- } )
104- if ( changed ) updateWidgets ( updated )
97+ // Persist only after intentional drag/resize. Do not use onLayoutChange:
98+ // responsive breakpoint compaction previously overwrote the saved layout
99+ // (and auto-saved it) when the window or DevTools width changed (#906).
100+ const handleLayoutCommit = useCallback ( ( newLayout ) => {
101+ if ( lockedRef . current ) return
102+ const updated = mergeLayoutIntoWidgets ( widgetsRef . current , newLayout )
103+ if ( updated ) updateWidgets ( updated )
105104 } , [ updateWidgets ] )
106105
107106 const availableRows = computeAvailableRows ( containerHeight )
108107
109108 const handleAddWidget = ( widget ) => {
110- skipLayoutChangeRef . current = true
111109 if ( locked ) setLocked ( false )
112110 const meta = getWidgetMeta ( widget . type )
113111 const clampedH = fitWidgetHeight ( widget . h ?? meta ?. defaultH ?? 3 , meta ?. minH ?? 2 , availableRows )
114112 updateWidgets ( [ ...widgets , { ...widget , h : clampedH } ] )
115113 }
116114
117115 const handleRemoveWidget = ( widgetId ) => {
118- skipLayoutChangeRef . current = true
119116 updateWidgets ( widgets . filter ( ( w ) => w . id !== widgetId ) )
120117 }
121118
122119 const handleDuplicateWidget = ( widget ) => {
123- skipLayoutChangeRef . current = true
124120 const meta = getWidgetMeta ( widget . type )
125121 const clampedH = fitWidgetHeight ( widget . h ?? meta ?. defaultH ?? 3 , meta ?. minH ?? 2 , availableRows )
126122 const copy = { ...widget , id : `${ widget . type } -${ Date . now ( ) } ` , x : 0 , y : Infinity , h : clampedH }
@@ -331,16 +327,19 @@ function DashboardGrid() {
331327 </ div >
332328 ) }
333329 { ! loading && activeDashboardId && widgets . length > 0 && containerWidth > 0 && (
334- < ResponsiveGridLayout
330+ < GridLayout
335331 className = "layout"
336- layouts = { { lg : layout } }
337- breakpoints = { { lg : 1200 , md : 996 , sm : 768 , xs : 480 } }
338- cols = { { lg : 12 , md : 10 , sm : 6 , xs : 4 } }
339- rowHeight = { GRID_ROW_HEIGHT }
332+ layout = { layout }
340333 width = { containerWidth - 24 }
334+ gridConfig = { {
335+ cols : GRID_COLS ,
336+ rowHeight : GRID_ROW_HEIGHT ,
337+ margin : [ GRID_MARGIN , GRID_MARGIN ] ,
338+ } }
341339 dragConfig = { { enabled : ! locked , handle : '.widget-drag-handle' } }
342340 resizeConfig = { { enabled : ! locked } }
343- onLayoutChange = { handleLayoutChange }
341+ onDragStop = { handleLayoutCommit }
342+ onResizeStop = { handleLayoutCommit }
344343 >
345344 { widgets . map ( ( widget ) => {
346345 const meta = getWidgetMeta ( widget . type )
@@ -390,7 +389,7 @@ function DashboardGrid() {
390389 </ div >
391390 )
392391 } ) }
393- </ ResponsiveGridLayout >
392+ </ GridLayout >
394393 ) }
395394 </ div >
396395
0 commit comments