@@ -76,13 +76,16 @@ class CustomDashboard extends DashboardView {
7676 currentCanvasFavorite : false ,
7777 hasUnsavedChanges : false ,
7878 isFullscreen : false ,
79+ // Track dragging element position for canvas auto-extend
80+ dragPosition : null ,
7981 } ;
8082 this . autoReloadTimer = null ;
8183 this . autoReloadProgressTimer = null ;
8284 this . autoReloadStartTime = null ;
8385 this . canvasPreferencesManager = null ;
8486 this . _isMounted = false ;
8587 this . _elementSeq = { } ;
88+ this . canvasRef = React . createRef ( ) ;
8689 }
8790
8891 componentDidMount ( ) {
@@ -622,22 +625,103 @@ class CustomDashboard extends DashboardView {
622625 }
623626 } ;
624627
628+ // Snap value to grid
629+ snapToGrid ( value , gridSize = 50 ) {
630+ return Math . round ( value / gridSize ) * gridSize ;
631+ }
632+
625633 handlePositionChange = ( id , x , y ) => {
634+ // Snap to grid and ensure position is not negative
635+ const safeX = Math . max ( 0 , this . snapToGrid ( x ) ) ;
636+ const safeY = Math . max ( 0 , this . snapToGrid ( y ) ) ;
626637 this . setState ( state => ( {
627638 elements : state . elements . map ( el =>
628- el . id === id ? { ...el , x, y } : el
639+ el . id === id ? { ...el , x : safeX , y : safeY } : el
629640 ) ,
630641 } ) , this . markUnsavedChanges ) ;
631642 } ;
632643
633644 handleSizeChange = ( id , width , height , x , y ) => {
645+ // Snap to grid and ensure position is not negative when resizing from left/top edges
646+ const safeX = Math . max ( 0 , this . snapToGrid ( x ) ) ;
647+ const safeY = Math . max ( 0 , this . snapToGrid ( y ) ) ;
648+ const snappedWidth = this . snapToGrid ( width ) ;
649+ const snappedHeight = this . snapToGrid ( height ) ;
634650 this . setState ( state => ( {
635651 elements : state . elements . map ( el =>
636- el . id === id ? { ...el , width, height, x, y } : el
652+ el . id === id ? { ...el , width : snappedWidth , height : snappedHeight , x : safeX , y : safeY } : el
637653 ) ,
638654 } ) , this . markUnsavedChanges ) ;
639655 } ;
640656
657+ handleDrag = ( id , x , y , width , height ) => {
658+ // Update drag position to trigger canvas auto-extend during drag
659+ this . setState ( {
660+ dragPosition : { id, x, y, width, height } ,
661+ } ) ;
662+ } ;
663+
664+ handleDragEnd = ( ) => {
665+ // Clear drag position when drag ends
666+ this . setState ( { dragPosition : null } ) ;
667+ } ;
668+
669+ handleResize = ( id , width , height , x , y ) => {
670+ // Update drag position to trigger canvas auto-extend during resize
671+ this . setState ( {
672+ dragPosition : { id, x, y, width, height } ,
673+ } ) ;
674+ } ;
675+
676+ handleResizeEnd = ( ) => {
677+ // Clear drag position when resize ends
678+ this . setState ( { dragPosition : null } ) ;
679+ } ;
680+
681+ // Calculate the required canvas size based on all elements and current drag position
682+ // Returns dimensions only when content extends beyond the default CSS size
683+ getCanvasSize ( ) {
684+ const { elements, dragPosition } = this . state ;
685+ const padding = 50 ; // Extra padding to allow easy placement at edges
686+
687+ let maxRight = 0 ;
688+ let maxBottom = 0 ;
689+
690+ // Calculate bounds from all elements
691+ elements . forEach ( el => {
692+ const right = el . x + el . width ;
693+ const bottom = el . y + el . height ;
694+ if ( right > maxRight ) {
695+ maxRight = right ;
696+ }
697+ if ( bottom > maxBottom ) {
698+ maxBottom = bottom ;
699+ }
700+ } ) ;
701+
702+ // Include current drag position if dragging
703+ if ( dragPosition ) {
704+ const dragRight = dragPosition . x + dragPosition . width ;
705+ const dragBottom = dragPosition . y + dragPosition . height ;
706+ if ( dragRight > maxRight ) {
707+ maxRight = dragRight ;
708+ }
709+ if ( dragBottom > maxBottom ) {
710+ maxBottom = dragBottom ;
711+ }
712+ }
713+
714+ // Add padding to content bounds
715+ const contentWidth = maxRight + padding ;
716+ const contentHeight = maxBottom + padding ;
717+
718+ // Return the content-based dimensions (CSS handles minimum via width:100% and min-height)
719+ return {
720+ minWidth : contentWidth ,
721+ minHeight : contentHeight ,
722+ } ;
723+ }
724+
641725 handleDeleteElement = ( id ) => {
642726 this . setState ( state => ( {
643727 elements : state . elements . filter ( el => el . id !== id ) ,
@@ -1065,13 +1149,30 @@ class CustomDashboard extends DashboardView {
10651149 wrapperClasses . push ( styles . fullscreen ) ;
10661150 }
10671151
1152+ // Calculate dynamic canvas size based on element positions
1153+ const canvasSize = this . getCanvasSize ( ) ;
1154+
10681155 return (
10691156 < div className = { wrapperClasses . join ( ' ' ) } >
10701157 < div
1158+ ref = { this . canvasRef }
10711159 className = { styles . canvas }
10721160 onClick = { this . handleDeselectElement }
10731161 tabIndex = { 0 }
10741162 >
1163+ { /* Invisible sizing element that expands the canvas when elements extend beyond */ }
1164+ { ( canvasSize . minWidth > 0 || canvasSize . minHeight > 0 ) && (
1165+ < div
1166+ style = { {
1167+ position : 'absolute' ,
1168+ top : 0 ,
1169+ left : 0 ,
1170+ width : canvasSize . minWidth ,
1171+ height : canvasSize . minHeight ,
1172+ pointerEvents : 'none' ,
1173+ } }
1174+ />
1175+ ) }
10751176 { elements . length === 0 && ! isFullscreen ? (
10761177 < EmptyState
10771178 icon = "canvas-outline"
@@ -1087,8 +1188,16 @@ class CustomDashboard extends DashboardView {
10871188 element = { element }
10881189 isSelected = { element . id === selectedElement }
10891190 onSelect = { this . handleSelectElement }
1090- onPositionChange = { this . handlePositionChange }
1091- onSizeChange = { this . handleSizeChange }
1191+ onPositionChange = { ( id , x , y ) => {
1192+ this . handleDragEnd ( ) ;
1193+ this . handlePositionChange ( id , x , y ) ;
1194+ } }
1195+ onSizeChange = { ( id , width , height , x , y ) => {
1196+ this . handleResizeEnd ( ) ;
1197+ this . handleSizeChange ( id , width , height , x , y ) ;
1198+ } }
1199+ onDrag = { this . handleDrag }
1200+ onResize = { this . handleResize }
10921201 >
10931202 { this . renderElementContent ( element ) }
10941203 </ CanvasElement >
0 commit comments