@@ -3915,51 +3915,107 @@ This is a fully client-side application. Your content never leaves your browser
39153915 let isFrDocked = false ;
39163916 let dragOffset = { x : 0 , y : 0 } ;
39173917 let isPanelDragging = false ;
3918+ let lastFloatingLeft = null ;
3919+ let lastFloatingTop = null ;
3920+ let lastFloatingRight = null ;
39183921
39193922 function initFindReplacePanelDrag ( ) {
39203923 const handle = document . getElementById ( 'find-replace-drag-handle' ) ;
39213924 const panel = document . getElementById ( 'find-replace-modal' ) ;
39223925 if ( ! handle || ! panel ) return ;
39233926
3924- handle . addEventListener ( 'mousedown' , ( e ) => {
3925- if ( isFrDocked ) return ;
3926- if ( e . target . closest ( '.find-replace-header-actions' ) ) return ;
3927+ const startDrag = ( clientX , clientY ) => {
39273928 isPanelDragging = true ;
3928- dragOffset . x = e . clientX - panel . offsetLeft ;
3929- dragOffset . y = e . clientY - panel . offsetTop ;
3929+ dragOffset . x = clientX - panel . offsetLeft ;
3930+ dragOffset . y = clientY - panel . offsetTop ;
39303931 document . body . classList . add ( 'resizing' ) ;
3931- } ) ;
3932+ } ;
39323933
3933- document . addEventListener ( 'mousemove' , ( e ) => {
3934- if ( ! isPanelDragging || isFrDocked ) return ;
3935- const x = e . clientX - dragOffset . x ;
3936- const y = e . clientY - dragOffset . y ;
3934+ const moveDrag = ( clientX , clientY ) => {
3935+ const x = clientX - dragOffset . x ;
3936+ const y = clientY - dragOffset . y ;
39373937
39383938 // Keep panel inside viewport boundaries
39393939 const maxX = window . innerWidth - panel . offsetWidth ;
39403940 const maxY = window . innerHeight - panel . offsetHeight ;
3941- panel . style . left = `${ Math . max ( 0 , Math . min ( maxX , x ) ) } px` ;
3942- panel . style . top = `${ Math . max ( 0 , Math . min ( maxY , y ) ) } px` ;
3941+ const newLeft = `${ Math . max ( 0 , Math . min ( maxX , x ) ) } px` ;
3942+ const newTop = `${ Math . max ( 0 , Math . min ( maxY , y ) ) } px` ;
3943+ panel . style . left = newLeft ;
3944+ panel . style . top = newTop ;
39433945 panel . style . right = 'auto' ;
3944- } ) ;
39453946
3946- document . addEventListener ( 'mouseup' , ( ) => {
3947+ lastFloatingLeft = newLeft ;
3948+ lastFloatingTop = newTop ;
3949+ lastFloatingRight = 'auto' ;
3950+ } ;
3951+
3952+ const stopDrag = ( ) => {
39473953 if ( isPanelDragging ) {
39483954 isPanelDragging = false ;
39493955 document . body . classList . remove ( 'resizing' ) ;
39503956 }
3957+ } ;
3958+
3959+ // Mouse events
3960+ handle . addEventListener ( 'mousedown' , ( e ) => {
3961+ if ( isFrDocked ) return ;
3962+ if ( window . innerWidth < 768 ) return ; // Do NOT allow dragging on mobile layouts
3963+ if ( e . target . closest ( '.find-replace-header-actions' ) ) return ;
3964+ startDrag ( e . clientX , e . clientY ) ;
39513965 } ) ;
3966+
3967+ document . addEventListener ( 'mousemove' , ( e ) => {
3968+ if ( ! isPanelDragging || isFrDocked ) return ;
3969+ moveDrag ( e . clientX , e . clientY ) ;
3970+ } ) ;
3971+
3972+ document . addEventListener ( 'mouseup' , stopDrag ) ;
3973+
3974+ // Touch events for tablets
3975+ handle . addEventListener ( 'touchstart' , ( e ) => {
3976+ if ( isFrDocked ) return ;
3977+ if ( window . innerWidth < 768 ) return ; // Do NOT allow dragging on mobile layouts
3978+ if ( e . target . closest ( '.find-replace-header-actions' ) ) return ;
3979+ if ( e . touches && e . touches [ 0 ] ) {
3980+ startDrag ( e . touches [ 0 ] . clientX , e . touches [ 0 ] . clientY ) ;
3981+ }
3982+ } , { passive : true } ) ;
3983+
3984+ document . addEventListener ( 'touchmove' , ( e ) => {
3985+ if ( ! isPanelDragging || isFrDocked ) return ;
3986+ if ( e . touches && e . touches [ 0 ] ) {
3987+ moveDrag ( e . touches [ 0 ] . clientX , e . touches [ 0 ] . clientY ) ;
3988+ }
3989+ } , { passive : true } ) ;
3990+
3991+ document . addEventListener ( 'touchend' , stopDrag ) ;
39523992 }
39533993
39543994 let frPreferredDocked = false ;
39553995
39563996 function toggleFrDockMode ( forceFloat = false ) {
3997+ // If forceFloat is an Event (e.g. from click listener directly), treat as false
3998+ if ( forceFloat instanceof Event || ( forceFloat && typeof forceFloat === 'object' ) ) {
3999+ forceFloat = false ;
4000+ }
4001+
39574002 const panel = document . getElementById ( 'find-replace-modal' ) ;
39584003 const dockBtn = document . getElementById ( 'find-replace-dock' ) ;
39594004 const contentCont = document . querySelector ( '.content-container' ) ;
39604005 if ( ! panel || ! dockBtn || ! contentCont ) return ;
39614006
3962- if ( window . innerWidth <= 768 || forceFloat ) {
4007+ // Save active element focus and selection before DOM movement
4008+ const activeEl = document . activeElement ;
4009+ const activeId = activeEl ? activeEl . id : null ;
4010+ const isInput = activeEl && ( activeEl . tagName === 'INPUT' || activeEl . tagName === 'SELECT' || activeEl . tagName === 'TEXTAREA' ) ;
4011+ let selStart = 0 ;
4012+ let selEnd = 0 ;
4013+ if ( isInput && typeof activeEl . selectionStart === 'number' ) {
4014+ selStart = activeEl . selectionStart ;
4015+ selEnd = activeEl . selectionEnd ;
4016+ }
4017+
4018+ if ( window . innerWidth < 1080 || forceFloat ) {
39634019 isFrDocked = false ;
39644020 panel . classList . remove ( 'docked' ) ;
39654021 if ( panel . parentElement !== document . body ) {
@@ -3968,15 +4024,26 @@ This is a fully client-side application. Your content never leaves your browser
39684024 contentCont . classList . remove ( 'fr-docked' ) ;
39694025 contentCont . style . setProperty ( '--dock-width' , '0px' ) ;
39704026
3971- panel . style . top = '' ;
3972- panel . style . left = '' ;
3973- panel . style . right = '' ;
4027+ panel . style . left = lastFloatingLeft !== null ? lastFloatingLeft : '' ;
4028+ panel . style . top = lastFloatingTop !== null ? lastFloatingTop : '' ;
4029+ panel . style . right = lastFloatingRight !== null ? lastFloatingRight : '' ;
39744030
39754031 dockBtn . innerHTML = '<i class="bi bi-layout-sidebar-reverse"></i>' ;
39764032 dockBtn . title = "Toggle Dock Mode" ;
39774033
39784034 panel . style . display = 'flex' ;
39794035 applyPaneWidths ( ) ;
4036+
4037+ // Restore focus and selection
4038+ if ( activeId ) {
4039+ const el = document . getElementById ( activeId ) ;
4040+ if ( el ) {
4041+ el . focus ( ) ;
4042+ if ( isInput && typeof el . selectionStart === 'number' ) {
4043+ el . setSelectionRange ( selStart , selEnd ) ;
4044+ }
4045+ }
4046+ }
39804047 return ;
39814048 }
39824049
@@ -4007,9 +4074,9 @@ This is a fully client-side application. Your content never leaves your browser
40074074 contentCont . classList . remove ( 'fr-docked' ) ;
40084075 contentCont . style . setProperty ( '--dock-width' , '0px' ) ;
40094076
4010- panel . style . top = '' ;
4011- panel . style . left = '' ;
4012- panel . style . right = '' ;
4077+ panel . style . left = lastFloatingLeft !== null ? lastFloatingLeft : '' ;
4078+ panel . style . top = lastFloatingTop !== null ? lastFloatingTop : '' ;
4079+ panel . style . right = lastFloatingRight !== null ? lastFloatingRight : '' ;
40134080
40144081 dockBtn . innerHTML = '<i class="bi bi-layout-sidebar-reverse"></i>' ;
40154082 dockBtn . title = "Toggle Dock Mode" ;
@@ -4018,6 +4085,17 @@ This is a fully client-side application. Your content never leaves your browser
40184085 // Ensure display is flex and recalculate split panes
40194086 panel . style . display = 'flex' ;
40204087 applyPaneWidths ( ) ;
4088+
4089+ // Restore focus and selection after layout change
4090+ if ( activeId ) {
4091+ const el = document . getElementById ( activeId ) ;
4092+ if ( el ) {
4093+ el . focus ( ) ;
4094+ if ( isInput && typeof el . selectionStart === 'number' ) {
4095+ el . setSelectionRange ( selStart , selEnd ) ;
4096+ }
4097+ }
4098+ }
40214099 }
40224100
40234101 function updateFindControls ( ) {
@@ -4143,7 +4221,7 @@ This is a fully client-side application. Your content never leaves your browser
41434221 let wasDockedPref = localStorage . getItem ( 'find-replace-docked' ) === 'true' ;
41444222
41454223 // Force floating-only mode on mobile/tablet viewports
4146- if ( window . innerWidth <= 768 ) {
4224+ if ( window . innerWidth < 1080 ) {
41474225 wasDockedPref = false ;
41484226 }
41494227
@@ -4380,10 +4458,23 @@ This is a fully client-side application. Your content never leaves your browser
43804458 } ) ;
43814459 }
43824460
4461+ // Reset position handler
4462+ const resetBtn = document . getElementById ( 'find-replace-reset' ) ;
4463+ if ( resetBtn ) {
4464+ resetBtn . addEventListener ( 'click' , ( ) => {
4465+ lastFloatingLeft = null ;
4466+ lastFloatingTop = null ;
4467+ lastFloatingRight = null ;
4468+ modal . style . left = '' ;
4469+ modal . style . top = '' ;
4470+ modal . style . right = '' ;
4471+ } ) ;
4472+ }
4473+
43834474 // Dock toggle handler
43844475 const dockBtn = document . getElementById ( 'find-replace-dock' ) ;
43854476 if ( dockBtn ) {
4386- dockBtn . addEventListener ( 'click' , toggleFrDockMode ) ;
4477+ dockBtn . addEventListener ( 'click' , ( ) => toggleFrDockMode ( false ) ) ;
43874478 }
43884479
43894480 // Advanced Drawer Toggle
@@ -4619,7 +4710,7 @@ This is a fully client-side application. Your content never leaves your browser
46194710 }
46204711
46214712 function startResize ( e ) {
4622- if ( window . innerWidth <= 768 ) return ;
4713+ if ( window . innerWidth < 1080 ) return ;
46234714 if ( currentViewMode !== 'split' ) return ;
46244715 e . preventDefault ( ) ;
46254716 isResizing = true ;
@@ -4628,7 +4719,7 @@ This is a fully client-side application. Your content never leaves your browser
46284719 }
46294720
46304721 function startResizeTouch ( e ) {
4631- if ( window . innerWidth <= 768 ) return ;
4722+ if ( window . innerWidth < 1080 ) return ;
46324723 if ( currentViewMode !== 'split' ) return ;
46334724 e . preventDefault ( ) ;
46344725 isResizing = true ;
@@ -4675,7 +4766,7 @@ This is a fully client-side application. Your content never leaves your browser
46754766 }
46764767
46774768 function applyPaneWidths ( ) {
4678- if ( window . innerWidth <= 768 ) {
4769+ if ( window . innerWidth < 1080 ) {
46794770 resetPaneWidths ( ) ;
46804771 return ;
46814772 }
@@ -4787,11 +4878,36 @@ This is a fully client-side application. Your content never leaves your browser
47874878
47884879 // Initialize resizer - Story 1.3
47894880 initResizer ( ) ;
4881+ function constrainFloatingPanelPosition ( ) {
4882+ const panel = document . getElementById ( 'find-replace-modal' ) ;
4883+ if ( ! panel || isFrDocked || panel . style . display === 'none' ) return ;
4884+ if ( window . innerWidth < 768 ) return ; // Mobile layout forces fixed responsive positioning via CSS
4885+
4886+ // Only adjust if the inline style has custom dragged coordinates
4887+ if ( ! panel . style . left || panel . style . left === 'auto' ) return ;
4888+
4889+ const leftVal = parseFloat ( panel . style . left ) || 0 ;
4890+ const topVal = parseFloat ( panel . style . top ) || 0 ;
4891+
4892+ const maxX = window . innerWidth - panel . offsetWidth ;
4893+ const maxY = window . innerHeight - panel . offsetHeight ;
4894+
4895+ const constrainedLeft = `${ Math . max ( 0 , Math . min ( maxX , leftVal ) ) } px` ;
4896+ const constrainedTop = `${ Math . max ( 0 , Math . min ( maxY , topVal ) ) } px` ;
4897+
4898+ panel . style . left = constrainedLeft ;
4899+ panel . style . top = constrainedTop ;
4900+
4901+ lastFloatingLeft = constrainedLeft ;
4902+ lastFloatingTop = constrainedTop ;
4903+ }
4904+
47904905 window . addEventListener ( 'resize' , ( ) => {
47914906 scheduleLineNumberUpdate ( ) ;
4792- if ( window . innerWidth <= 768 && isFrDocked && isFindModalOpen ) {
4907+ if ( window . innerWidth < 1080 && isFrDocked && isFindModalOpen ) {
47934908 toggleFrDockMode ( true ) ;
47944909 }
4910+ constrainFloatingPanelPosition ( ) ;
47954911 } ) ;
47964912
47974913 // View Mode Button Event Listeners - Story 1.1
0 commit comments