diff --git a/desktop-app/resources/index.html b/desktop-app/resources/index.html
index 5de6b45..aabec7f 100644
--- a/desktop-app/resources/index.html
+++ b/desktop-app/resources/index.html
@@ -414,6 +414,7 @@
Find & Replace
diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js
index 559ab6a..cf003b0 100644
--- a/desktop-app/resources/js/script.js
+++ b/desktop-app/resources/js/script.js
@@ -3915,51 +3915,107 @@ This is a fully client-side application. Your content never leaves your browser
let isFrDocked = false;
let dragOffset = { x: 0, y: 0 };
let isPanelDragging = false;
+ let lastFloatingLeft = null;
+ let lastFloatingTop = null;
+ let lastFloatingRight = null;
function initFindReplacePanelDrag() {
const handle = document.getElementById('find-replace-drag-handle');
const panel = document.getElementById('find-replace-modal');
if (!handle || !panel) return;
- handle.addEventListener('mousedown', (e) => {
- if (isFrDocked) return;
- if (e.target.closest('.find-replace-header-actions')) return;
+ const startDrag = (clientX, clientY) => {
isPanelDragging = true;
- dragOffset.x = e.clientX - panel.offsetLeft;
- dragOffset.y = e.clientY - panel.offsetTop;
+ dragOffset.x = clientX - panel.offsetLeft;
+ dragOffset.y = clientY - panel.offsetTop;
document.body.classList.add('resizing');
- });
+ };
- document.addEventListener('mousemove', (e) => {
- if (!isPanelDragging || isFrDocked) return;
- const x = e.clientX - dragOffset.x;
- const y = e.clientY - dragOffset.y;
+ const moveDrag = (clientX, clientY) => {
+ const x = clientX - dragOffset.x;
+ const y = clientY - dragOffset.y;
// Keep panel inside viewport boundaries
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
- panel.style.left = `${Math.max(0, Math.min(maxX, x))}px`;
- panel.style.top = `${Math.max(0, Math.min(maxY, y))}px`;
+ const newLeft = `${Math.max(0, Math.min(maxX, x))}px`;
+ const newTop = `${Math.max(0, Math.min(maxY, y))}px`;
+ panel.style.left = newLeft;
+ panel.style.top = newTop;
panel.style.right = 'auto';
- });
- document.addEventListener('mouseup', () => {
+ lastFloatingLeft = newLeft;
+ lastFloatingTop = newTop;
+ lastFloatingRight = 'auto';
+ };
+
+ const stopDrag = () => {
if (isPanelDragging) {
isPanelDragging = false;
document.body.classList.remove('resizing');
}
+ };
+
+ // Mouse events
+ handle.addEventListener('mousedown', (e) => {
+ if (isFrDocked) return;
+ if (window.innerWidth < 768) return; // Do NOT allow dragging on mobile layouts
+ if (e.target.closest('.find-replace-header-actions')) return;
+ startDrag(e.clientX, e.clientY);
});
+
+ document.addEventListener('mousemove', (e) => {
+ if (!isPanelDragging || isFrDocked) return;
+ moveDrag(e.clientX, e.clientY);
+ });
+
+ document.addEventListener('mouseup', stopDrag);
+
+ // Touch events for tablets
+ handle.addEventListener('touchstart', (e) => {
+ if (isFrDocked) return;
+ if (window.innerWidth < 768) return; // Do NOT allow dragging on mobile layouts
+ if (e.target.closest('.find-replace-header-actions')) return;
+ if (e.touches && e.touches[0]) {
+ startDrag(e.touches[0].clientX, e.touches[0].clientY);
+ }
+ }, { passive: true });
+
+ document.addEventListener('touchmove', (e) => {
+ if (!isPanelDragging || isFrDocked) return;
+ if (e.touches && e.touches[0]) {
+ moveDrag(e.touches[0].clientX, e.touches[0].clientY);
+ }
+ }, { passive: true });
+
+ document.addEventListener('touchend', stopDrag);
}
let frPreferredDocked = false;
function toggleFrDockMode(forceFloat = false) {
+ // If forceFloat is an Event (e.g. from click listener directly), treat as false
+ if (forceFloat instanceof Event || (forceFloat && typeof forceFloat === 'object')) {
+ forceFloat = false;
+ }
+
const panel = document.getElementById('find-replace-modal');
const dockBtn = document.getElementById('find-replace-dock');
const contentCont = document.querySelector('.content-container');
if (!panel || !dockBtn || !contentCont) return;
- if (window.innerWidth <= 768 || forceFloat) {
+ // Save active element focus and selection before DOM movement
+ const activeEl = document.activeElement;
+ const activeId = activeEl ? activeEl.id : null;
+ const isInput = activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'SELECT' || activeEl.tagName === 'TEXTAREA');
+ let selStart = 0;
+ let selEnd = 0;
+ if (isInput && typeof activeEl.selectionStart === 'number') {
+ selStart = activeEl.selectionStart;
+ selEnd = activeEl.selectionEnd;
+ }
+
+ if (window.innerWidth < 1080 || forceFloat) {
isFrDocked = false;
panel.classList.remove('docked');
if (panel.parentElement !== document.body) {
@@ -3968,15 +4024,26 @@ This is a fully client-side application. Your content never leaves your browser
contentCont.classList.remove('fr-docked');
contentCont.style.setProperty('--dock-width', '0px');
- panel.style.top = '';
- panel.style.left = '';
- panel.style.right = '';
+ panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
+ panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
+ panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';
dockBtn.innerHTML = '';
dockBtn.title = "Toggle Dock Mode";
panel.style.display = 'flex';
applyPaneWidths();
+
+ // Restore focus and selection
+ if (activeId) {
+ const el = document.getElementById(activeId);
+ if (el) {
+ el.focus();
+ if (isInput && typeof el.selectionStart === 'number') {
+ el.setSelectionRange(selStart, selEnd);
+ }
+ }
+ }
return;
}
@@ -4007,9 +4074,9 @@ This is a fully client-side application. Your content never leaves your browser
contentCont.classList.remove('fr-docked');
contentCont.style.setProperty('--dock-width', '0px');
- panel.style.top = '';
- panel.style.left = '';
- panel.style.right = '';
+ panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
+ panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
+ panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';
dockBtn.innerHTML = '';
dockBtn.title = "Toggle Dock Mode";
@@ -4018,6 +4085,17 @@ This is a fully client-side application. Your content never leaves your browser
// Ensure display is flex and recalculate split panes
panel.style.display = 'flex';
applyPaneWidths();
+
+ // Restore focus and selection after layout change
+ if (activeId) {
+ const el = document.getElementById(activeId);
+ if (el) {
+ el.focus();
+ if (isInput && typeof el.selectionStart === 'number') {
+ el.setSelectionRange(selStart, selEnd);
+ }
+ }
+ }
}
function updateFindControls() {
@@ -4143,7 +4221,7 @@ This is a fully client-side application. Your content never leaves your browser
let wasDockedPref = localStorage.getItem('find-replace-docked') === 'true';
// Force floating-only mode on mobile/tablet viewports
- if (window.innerWidth <= 768) {
+ if (window.innerWidth < 1080) {
wasDockedPref = false;
}
@@ -4380,10 +4458,23 @@ This is a fully client-side application. Your content never leaves your browser
});
}
+ // Reset position handler
+ const resetBtn = document.getElementById('find-replace-reset');
+ if (resetBtn) {
+ resetBtn.addEventListener('click', () => {
+ lastFloatingLeft = null;
+ lastFloatingTop = null;
+ lastFloatingRight = null;
+ modal.style.left = '';
+ modal.style.top = '';
+ modal.style.right = '';
+ });
+ }
+
// Dock toggle handler
const dockBtn = document.getElementById('find-replace-dock');
if (dockBtn) {
- dockBtn.addEventListener('click', toggleFrDockMode);
+ dockBtn.addEventListener('click', () => toggleFrDockMode(false));
}
// Advanced Drawer Toggle
@@ -4619,7 +4710,7 @@ This is a fully client-side application. Your content never leaves your browser
}
function startResize(e) {
- if (window.innerWidth <= 768) return;
+ if (window.innerWidth < 1080) return;
if (currentViewMode !== 'split') return;
e.preventDefault();
isResizing = true;
@@ -4628,7 +4719,7 @@ This is a fully client-side application. Your content never leaves your browser
}
function startResizeTouch(e) {
- if (window.innerWidth <= 768) return;
+ if (window.innerWidth < 1080) return;
if (currentViewMode !== 'split') return;
e.preventDefault();
isResizing = true;
@@ -4675,7 +4766,7 @@ This is a fully client-side application. Your content never leaves your browser
}
function applyPaneWidths() {
- if (window.innerWidth <= 768) {
+ if (window.innerWidth < 1080) {
resetPaneWidths();
return;
}
@@ -4787,11 +4878,36 @@ This is a fully client-side application. Your content never leaves your browser
// Initialize resizer - Story 1.3
initResizer();
+ function constrainFloatingPanelPosition() {
+ const panel = document.getElementById('find-replace-modal');
+ if (!panel || isFrDocked || panel.style.display === 'none') return;
+ if (window.innerWidth < 768) return; // Mobile layout forces fixed responsive positioning via CSS
+
+ // Only adjust if the inline style has custom dragged coordinates
+ if (!panel.style.left || panel.style.left === 'auto') return;
+
+ const leftVal = parseFloat(panel.style.left) || 0;
+ const topVal = parseFloat(panel.style.top) || 0;
+
+ const maxX = window.innerWidth - panel.offsetWidth;
+ const maxY = window.innerHeight - panel.offsetHeight;
+
+ const constrainedLeft = `${Math.max(0, Math.min(maxX, leftVal))}px`;
+ const constrainedTop = `${Math.max(0, Math.min(maxY, topVal))}px`;
+
+ panel.style.left = constrainedLeft;
+ panel.style.top = constrainedTop;
+
+ lastFloatingLeft = constrainedLeft;
+ lastFloatingTop = constrainedTop;
+ }
+
window.addEventListener('resize', () => {
scheduleLineNumberUpdate();
- if (window.innerWidth <= 768 && isFrDocked && isFindModalOpen) {
+ if (window.innerWidth < 1080 && isFrDocked && isFindModalOpen) {
toggleFrDockMode(true);
}
+ constrainFloatingPanelPosition();
});
// View Mode Button Event Listeners - Story 1.1
diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css
index ee4df05..e5f7569 100644
--- a/desktop-app/resources/styles.css
+++ b/desktop-app/resources/styles.css
@@ -3013,6 +3013,10 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
flex-shrink: 0;
}
+.find-replace-panel.docked #find-replace-reset {
+ display: none !important;
+}
+
.find-replace-header {
display: flex;
align-items: center;
@@ -3379,13 +3383,15 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
}
/* ========================================
- MOBILE FIND PANEL RESPONSIVE FIXES
+ MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
======================================== */
-@media (max-width: 768px) {
+@media (max-width: 1079px) {
#find-replace-dock {
display: none !important;
}
-
+}
+
+@media (max-width: 768px) {
/* Prevent full screen expansion of floating panel on small mobile viewports */
.find-replace-panel {
width: calc(100% - 24px) !important;
diff --git a/index.html b/index.html
index 73b66dc..b1449cd 100644
--- a/index.html
+++ b/index.html
@@ -411,6 +411,7 @@
Menu
Find & Replace
diff --git a/script.js b/script.js
index dfec580..cf003b0 100644
--- a/script.js
+++ b/script.js
@@ -3915,6 +3915,9 @@ This is a fully client-side application. Your content never leaves your browser
let isFrDocked = false;
let dragOffset = { x: 0, y: 0 };
let isPanelDragging = false;
+ let lastFloatingLeft = null;
+ let lastFloatingTop = null;
+ let lastFloatingRight = null;
function initFindReplacePanelDrag() {
const handle = document.getElementById('find-replace-drag-handle');
@@ -3935,9 +3938,15 @@ This is a fully client-side application. Your content never leaves your browser
// Keep panel inside viewport boundaries
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
- panel.style.left = `${Math.max(0, Math.min(maxX, x))}px`;
- panel.style.top = `${Math.max(0, Math.min(maxY, y))}px`;
+ const newLeft = `${Math.max(0, Math.min(maxX, x))}px`;
+ const newTop = `${Math.max(0, Math.min(maxY, y))}px`;
+ panel.style.left = newLeft;
+ panel.style.top = newTop;
panel.style.right = 'auto';
+
+ lastFloatingLeft = newLeft;
+ lastFloatingTop = newTop;
+ lastFloatingRight = 'auto';
};
const stopDrag = () => {
@@ -4015,9 +4024,9 @@ This is a fully client-side application. Your content never leaves your browser
contentCont.classList.remove('fr-docked');
contentCont.style.setProperty('--dock-width', '0px');
- panel.style.top = '';
- panel.style.left = '';
- panel.style.right = '';
+ panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
+ panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
+ panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';
dockBtn.innerHTML = '';
dockBtn.title = "Toggle Dock Mode";
@@ -4065,9 +4074,9 @@ This is a fully client-side application. Your content never leaves your browser
contentCont.classList.remove('fr-docked');
contentCont.style.setProperty('--dock-width', '0px');
- panel.style.top = '';
- panel.style.left = '';
- panel.style.right = '';
+ panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
+ panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
+ panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';
dockBtn.innerHTML = '';
dockBtn.title = "Toggle Dock Mode";
@@ -4449,6 +4458,19 @@ This is a fully client-side application. Your content never leaves your browser
});
}
+ // Reset position handler
+ const resetBtn = document.getElementById('find-replace-reset');
+ if (resetBtn) {
+ resetBtn.addEventListener('click', () => {
+ lastFloatingLeft = null;
+ lastFloatingTop = null;
+ lastFloatingRight = null;
+ modal.style.left = '';
+ modal.style.top = '';
+ modal.style.right = '';
+ });
+ }
+
// Dock toggle handler
const dockBtn = document.getElementById('find-replace-dock');
if (dockBtn) {
@@ -4870,8 +4892,14 @@ This is a fully client-side application. Your content never leaves your browser
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
- panel.style.left = `${Math.max(0, Math.min(maxX, leftVal))}px`;
- panel.style.top = `${Math.max(0, Math.min(maxY, topVal))}px`;
+ const constrainedLeft = `${Math.max(0, Math.min(maxX, leftVal))}px`;
+ const constrainedTop = `${Math.max(0, Math.min(maxY, topVal))}px`;
+
+ panel.style.left = constrainedLeft;
+ panel.style.top = constrainedTop;
+
+ lastFloatingLeft = constrainedLeft;
+ lastFloatingTop = constrainedTop;
}
window.addEventListener('resize', () => {
diff --git a/styles.css b/styles.css
index 5939487..e5f7569 100644
--- a/styles.css
+++ b/styles.css
@@ -3013,6 +3013,10 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
flex-shrink: 0;
}
+.find-replace-panel.docked #find-replace-reset {
+ display: none !important;
+}
+
.find-replace-header {
display: flex;
align-items: center;