@@ -3921,45 +3921,92 @@ This is a fully client-side application. Your content never leaves your browser
39213921 const panel = document . getElementById ( 'find-replace-modal' ) ;
39223922 if ( ! handle || ! panel ) return ;
39233923
3924- handle . addEventListener ( 'mousedown' , ( e ) => {
3925- if ( isFrDocked ) return ;
3926- if ( e . target . closest ( '.find-replace-header-actions' ) ) return ;
3924+ const startDrag = ( clientX , clientY ) => {
39273925 isPanelDragging = true ;
3928- dragOffset . x = e . clientX - panel . offsetLeft ;
3929- dragOffset . y = e . clientY - panel . offsetTop ;
3926+ dragOffset . x = clientX - panel . offsetLeft ;
3927+ dragOffset . y = clientY - panel . offsetTop ;
39303928 document . body . classList . add ( 'resizing' ) ;
3931- } ) ;
3929+ } ;
39323930
3933- document . addEventListener ( 'mousemove' , ( e ) => {
3934- if ( ! isPanelDragging || isFrDocked ) return ;
3935- const x = e . clientX - dragOffset . x ;
3936- const y = e . clientY - dragOffset . y ;
3931+ const moveDrag = ( clientX , clientY ) => {
3932+ const x = clientX - dragOffset . x ;
3933+ const y = clientY - dragOffset . y ;
39373934
39383935 // Keep panel inside viewport boundaries
39393936 const maxX = window . innerWidth - panel . offsetWidth ;
39403937 const maxY = window . innerHeight - panel . offsetHeight ;
39413938 panel . style . left = `${ Math . max ( 0 , Math . min ( maxX , x ) ) } px` ;
39423939 panel . style . top = `${ Math . max ( 0 , Math . min ( maxY , y ) ) } px` ;
39433940 panel . style . right = 'auto' ;
3944- } ) ;
3941+ } ;
39453942
3946- document . addEventListener ( 'mouseup' , ( ) => {
3943+ const stopDrag = ( ) => {
39473944 if ( isPanelDragging ) {
39483945 isPanelDragging = false ;
39493946 document . body . classList . remove ( 'resizing' ) ;
39503947 }
3948+ } ;
3949+
3950+ // Mouse events
3951+ handle . addEventListener ( 'mousedown' , ( e ) => {
3952+ if ( isFrDocked ) return ;
3953+ if ( window . innerWidth < 768 ) return ; // Do NOT allow dragging on mobile layouts
3954+ if ( e . target . closest ( '.find-replace-header-actions' ) ) return ;
3955+ startDrag ( e . clientX , e . clientY ) ;
3956+ } ) ;
3957+
3958+ document . addEventListener ( 'mousemove' , ( e ) => {
3959+ if ( ! isPanelDragging || isFrDocked ) return ;
3960+ moveDrag ( e . clientX , e . clientY ) ;
39513961 } ) ;
3962+
3963+ document . addEventListener ( 'mouseup' , stopDrag ) ;
3964+
3965+ // Touch events for tablets
3966+ handle . addEventListener ( 'touchstart' , ( e ) => {
3967+ if ( isFrDocked ) return ;
3968+ if ( window . innerWidth < 768 ) return ; // Do NOT allow dragging on mobile layouts
3969+ if ( e . target . closest ( '.find-replace-header-actions' ) ) return ;
3970+ if ( e . touches && e . touches [ 0 ] ) {
3971+ startDrag ( e . touches [ 0 ] . clientX , e . touches [ 0 ] . clientY ) ;
3972+ }
3973+ } , { passive : true } ) ;
3974+
3975+ document . addEventListener ( 'touchmove' , ( e ) => {
3976+ if ( ! isPanelDragging || isFrDocked ) return ;
3977+ if ( e . touches && e . touches [ 0 ] ) {
3978+ moveDrag ( e . touches [ 0 ] . clientX , e . touches [ 0 ] . clientY ) ;
3979+ }
3980+ } , { passive : true } ) ;
3981+
3982+ document . addEventListener ( 'touchend' , stopDrag ) ;
39523983 }
39533984
39543985 let frPreferredDocked = false ;
39553986
39563987 function toggleFrDockMode ( forceFloat = false ) {
3988+ // If forceFloat is an Event (e.g. from click listener directly), treat as false
3989+ if ( forceFloat instanceof Event || ( forceFloat && typeof forceFloat === 'object' ) ) {
3990+ forceFloat = false ;
3991+ }
3992+
39573993 const panel = document . getElementById ( 'find-replace-modal' ) ;
39583994 const dockBtn = document . getElementById ( 'find-replace-dock' ) ;
39593995 const contentCont = document . querySelector ( '.content-container' ) ;
39603996 if ( ! panel || ! dockBtn || ! contentCont ) return ;
39613997
3962- if ( window . innerWidth <= 768 || forceFloat ) {
3998+ // Save active element focus and selection before DOM movement
3999+ const activeEl = document . activeElement ;
4000+ const activeId = activeEl ? activeEl . id : null ;
4001+ const isInput = activeEl && ( activeEl . tagName === 'INPUT' || activeEl . tagName === 'SELECT' || activeEl . tagName === 'TEXTAREA' ) ;
4002+ let selStart = 0 ;
4003+ let selEnd = 0 ;
4004+ if ( isInput && typeof activeEl . selectionStart === 'number' ) {
4005+ selStart = activeEl . selectionStart ;
4006+ selEnd = activeEl . selectionEnd ;
4007+ }
4008+
4009+ if ( window . innerWidth < 1080 || forceFloat ) {
39634010 isFrDocked = false ;
39644011 panel . classList . remove ( 'docked' ) ;
39654012 if ( panel . parentElement !== document . body ) {
@@ -3977,6 +4024,17 @@ This is a fully client-side application. Your content never leaves your browser
39774024
39784025 panel . style . display = 'flex' ;
39794026 applyPaneWidths ( ) ;
4027+
4028+ // Restore focus and selection
4029+ if ( activeId ) {
4030+ const el = document . getElementById ( activeId ) ;
4031+ if ( el ) {
4032+ el . focus ( ) ;
4033+ if ( isInput && typeof el . selectionStart === 'number' ) {
4034+ el . setSelectionRange ( selStart , selEnd ) ;
4035+ }
4036+ }
4037+ }
39804038 return ;
39814039 }
39824040
@@ -4018,6 +4076,17 @@ This is a fully client-side application. Your content never leaves your browser
40184076 // Ensure display is flex and recalculate split panes
40194077 panel . style . display = 'flex' ;
40204078 applyPaneWidths ( ) ;
4079+
4080+ // Restore focus and selection after layout change
4081+ if ( activeId ) {
4082+ const el = document . getElementById ( activeId ) ;
4083+ if ( el ) {
4084+ el . focus ( ) ;
4085+ if ( isInput && typeof el . selectionStart === 'number' ) {
4086+ el . setSelectionRange ( selStart , selEnd ) ;
4087+ }
4088+ }
4089+ }
40214090 }
40224091
40234092 function updateFindControls ( ) {
@@ -4143,7 +4212,7 @@ This is a fully client-side application. Your content never leaves your browser
41434212 let wasDockedPref = localStorage . getItem ( 'find-replace-docked' ) === 'true' ;
41444213
41454214 // Force floating-only mode on mobile/tablet viewports
4146- if ( window . innerWidth <= 768 ) {
4215+ if ( window . innerWidth < 1080 ) {
41474216 wasDockedPref = false ;
41484217 }
41494218
@@ -4383,7 +4452,7 @@ This is a fully client-side application. Your content never leaves your browser
43834452 // Dock toggle handler
43844453 const dockBtn = document . getElementById ( 'find-replace-dock' ) ;
43854454 if ( dockBtn ) {
4386- dockBtn . addEventListener ( 'click' , toggleFrDockMode ) ;
4455+ dockBtn . addEventListener ( 'click' , ( ) => toggleFrDockMode ( false ) ) ;
43874456 }
43884457
43894458 // Advanced Drawer Toggle
@@ -4619,7 +4688,7 @@ This is a fully client-side application. Your content never leaves your browser
46194688 }
46204689
46214690 function startResize ( e ) {
4622- if ( window . innerWidth <= 768 ) return ;
4691+ if ( window . innerWidth < 1080 ) return ;
46234692 if ( currentViewMode !== 'split' ) return ;
46244693 e . preventDefault ( ) ;
46254694 isResizing = true ;
@@ -4628,7 +4697,7 @@ This is a fully client-side application. Your content never leaves your browser
46284697 }
46294698
46304699 function startResizeTouch ( e ) {
4631- if ( window . innerWidth <= 768 ) return ;
4700+ if ( window . innerWidth < 1080 ) return ;
46324701 if ( currentViewMode !== 'split' ) return ;
46334702 e . preventDefault ( ) ;
46344703 isResizing = true ;
@@ -4675,7 +4744,7 @@ This is a fully client-side application. Your content never leaves your browser
46754744 }
46764745
46774746 function applyPaneWidths ( ) {
4678- if ( window . innerWidth <= 768 ) {
4747+ if ( window . innerWidth < 1080 ) {
46794748 resetPaneWidths ( ) ;
46804749 return ;
46814750 }
@@ -4787,11 +4856,30 @@ This is a fully client-side application. Your content never leaves your browser
47874856
47884857 // Initialize resizer - Story 1.3
47894858 initResizer ( ) ;
4859+ function constrainFloatingPanelPosition ( ) {
4860+ const panel = document . getElementById ( 'find-replace-modal' ) ;
4861+ if ( ! panel || isFrDocked || panel . style . display === 'none' ) return ;
4862+ if ( window . innerWidth < 768 ) return ; // Mobile layout forces fixed responsive positioning via CSS
4863+
4864+ // Only adjust if the inline style has custom dragged coordinates
4865+ if ( ! panel . style . left || panel . style . left === 'auto' ) return ;
4866+
4867+ const leftVal = parseFloat ( panel . style . left ) || 0 ;
4868+ const topVal = parseFloat ( panel . style . top ) || 0 ;
4869+
4870+ const maxX = window . innerWidth - panel . offsetWidth ;
4871+ const maxY = window . innerHeight - panel . offsetHeight ;
4872+
4873+ panel . style . left = `${ Math . max ( 0 , Math . min ( maxX , leftVal ) ) } px` ;
4874+ panel . style . top = `${ Math . max ( 0 , Math . min ( maxY , topVal ) ) } px` ;
4875+ }
4876+
47904877 window . addEventListener ( 'resize' , ( ) => {
47914878 scheduleLineNumberUpdate ( ) ;
4792- if ( window . innerWidth <= 768 && isFrDocked && isFindModalOpen ) {
4879+ if ( window . innerWidth < 1080 && isFrDocked && isFindModalOpen ) {
47934880 toggleFrDockMode ( true ) ;
47944881 }
4882+ constrainFloatingPanelPosition ( ) ;
47954883 } ) ;
47964884
47974885 // View Mode Button Event Listeners - Story 1.1
0 commit comments