fix(ui): restore all-edge modal snap zones (#2260)

This commit is contained in:
nubs
2026-06-15 10:36:34 +00:00
committed by GitHub
parent 3c0e9fcb25
commit 55b4a5e6ff
2 changed files with 138 additions and 13 deletions
+21 -13
View File
@@ -6,16 +6,13 @@
* when the cursor is near a snap zone. On release, snaps the modal-content
* to fill that zone with a springy animation.
*
* Snap zones (9):
* - top edge (10% strip) → maximize
* - top-left corner → top-left quarter
* - top-right corner → top-right quarter
* Snap zones:
* - over top edge → fullscreen
* - top strip → maximize
* - top edge → top half
* - left edge → left half
* - right edge → right half
* - bottom-left corner → bottom-left quarter
* - bottom-right corner → bottom-right quarter
* - bottom edge → bottom half
* - sidebar edge (if present) → snap next to the sidebar
*
* Mobile (≤768px) is excluded — the swipe-dismiss UX takes precedence.
*
@@ -24,7 +21,6 @@
*/
const EDGE_THRESHOLD_PX = 24; // how close to an edge counts as "near"
const CORNER_THRESHOLD_PX = 64; // corner box size
const TOP_FULL_STRIP_PX = 8; // top strip → maximize
let _ghost = null;
@@ -111,9 +107,13 @@ function _zoneForPointer(x, y) {
return { name: 'maximize', rect: { left: safe.left, top: safe.top, width: W, height: H } };
}
// Corner quarter-snaps DISABLED (user request) — only the top strip
// (maximize) and the right/bottom half-snaps remain. The LEFT-half snap
// is also disabled (the sidebar lives there; docking over it is awkward).
// Symmetric edge half-snaps. The safe rect already starts to the right of
// the sidebar/rail, so left-half fills the left side of the workspace
// without covering navigation.
if (y <= safe.top + EDGE_THRESHOLD_PX)
return { name: 'top-half', rect: { left: safe.left, top: safe.top, width: W, height: H / 2 } };
if (x <= safe.left + EDGE_THRESHOLD_PX)
return { name: 'left-half', rect: { left: safe.left, top: safe.top, width: W / 2, height: H } };
if (x >= safe.right - EDGE_THRESHOLD_PX)
return { name: 'right-half', rect: { left: safe.left + W / 2, top: safe.top, width: W / 2, height: H } };
if (y >= safe.bottom - EDGE_THRESHOLD_PX)
@@ -131,8 +131,7 @@ function _zoneForContent(content, x, y) {
// flip to top tabs via CSS when the window gets narrow.
if (modal && modal.id === 'settings-modal' && zone.name !== 'right-half') return null;
if (modal && (modal.id === 'cookbook-modal'
|| modal.id === 'theme-modal'
|| modal.id === 'memory-modal')
|| modal.id === 'theme-modal')
&& zone.name !== 'fullscreen') return null;
return zone;
}
@@ -304,6 +303,7 @@ function _reclampAll(animate = false) {
switch (name) {
case 'fullscreen': r = { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight }; break;
case 'maximize': r = { left: safe.left, top: safe.top, width: W, height: H }; break;
case 'top-half': r = { left: safe.left, top: safe.top, width: W, height: H/2 }; break;
case 'left-half': r = { left: safe.left, top: safe.top, width: W/2, height: H }; break;
case 'right-half': r = { left: safe.left + W/2, top: safe.top, width: W/2, height: H }; break;
case 'bottom-half': r = { left: safe.left, top: safe.top + H/2, width: W, height: H/2 }; break;
@@ -374,6 +374,14 @@ export function clearPreview() {
_activeZone = null;
}
export function _zoneForPointerForTests(x, y) {
return _zoneForPointer(x, y);
}
export function _zoneForContentForTests(content, x, y) {
return _zoneForContent(content, x, y);
}
// Snap a modal (its .modal-content) into a previously-detected zone.
export function snapModalToZone(modal, zone) {
if (!modal || !zone) return;